Merge branch 'main' into #126-vacation-request-reminders
# Conflicts: # app/Domain/Actions/VacationRequest/CreateAction.php # app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
This commit is contained in:
		@@ -4,13 +4,24 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Architecture\Providers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Foundation\Application;
 | 
			
		||||
use Illuminate\Notifications\ChannelManager;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Facades\Notification;
 | 
			
		||||
use Illuminate\Support\ServiceProvider;
 | 
			
		||||
use Toby\Infrastructure\Slack\Channels\SlackApiChannel;
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    {
 | 
			
		||||
        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("manageHolidays", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageVacationLimits", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("generateTimesheet", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("listMonthlyUsage", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageUsers", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageHolidays", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageVacationLimits", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("generateTimesheet", fn(User $user): bool => $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
 | 
			
		||||
    {
 | 
			
		||||
        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()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,6 @@ class CalendarGenerator
 | 
			
		||||
            ->approved()
 | 
			
		||||
            ->with("vacationRequest")
 | 
			
		||||
            ->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());
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(EmploymentForm $enum) => [
 | 
			
		||||
            fn(EmploymentForm $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "value" => $enum->value,
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ enum Role: string
 | 
			
		||||
        $cases = collect(Role::cases());
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(Role $enum) => [
 | 
			
		||||
            fn(Role $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "value" => $enum->value,
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -30,7 +30,7 @@ enum VacationType: string
 | 
			
		||||
        $cases = VacationType::all();
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(VacationType $enum) => [
 | 
			
		||||
            fn(VacationType $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "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";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Notifications;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Notifications\Notification;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
 | 
			
		||||
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): string
 | 
			
		||||
    {
 | 
			
		||||
        return __(":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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Notifications;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Bus\Queueable;
 | 
			
		||||
use Illuminate\Notifications\Notification;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
 | 
			
		||||
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): string
 | 
			
		||||
    {
 | 
			
		||||
        return __(":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);
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,17 @@ class VacationRequestCreatedNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    public function via(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ["mail"];
 | 
			
		||||
        return [Channels::MAIL, Channels::SLACK];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toSlack(): string
 | 
			
		||||
    {
 | 
			
		||||
        $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]);
 | 
			
		||||
 | 
			
		||||
        return implode("\n", [
 | 
			
		||||
            $this->buildDescription(),
 | 
			
		||||
            "<${url}|Zobacz szczegóły>",
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -80,18 +90,16 @@ class VacationRequestCreatedNotification extends Notification
 | 
			
		||||
    protected function buildDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        $name = $this->vacationRequest->name;
 | 
			
		||||
        $appName = config("app.name");
 | 
			
		||||
 | 
			
		||||
        if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) {
 | 
			
		||||
            return __("The vacation request :title has been created correctly in the :appName.", [
 | 
			
		||||
            return __("The vacation request :title from user :user has been created successfully.", [
 | 
			
		||||
                "user" => $this->vacationRequest->user->profile->full_name,
 | 
			
		||||
                "title" => $name,
 | 
			
		||||
                "appName" => $appName,
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return __("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [
 | 
			
		||||
        return __("The vacation request :title has been created successfully by user :creator on your behalf.", [
 | 
			
		||||
            "title" => $this->vacationRequest->name,
 | 
			
		||||
            "appName" => $appName,
 | 
			
		||||
            "creator" => $this->vacationRequest->creator->profile->full_name,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,17 @@ class VacationRequestStatusChangedNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    public function via(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ["mail"];
 | 
			
		||||
        return [Channels::MAIL, Channels::SLACK];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toSlack(): string
 | 
			
		||||
    {
 | 
			
		||||
        $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]);
 | 
			
		||||
 | 
			
		||||
        return implode("\n", [
 | 
			
		||||
            $this->buildDescription(),
 | 
			
		||||
            "<${url}|Zobacz szczegóły>",
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -43,27 +53,17 @@ class VacationRequestStatusChangedNotification extends Notification
 | 
			
		||||
    protected function buildMailMessage(string $url): MailMessage
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->user->profile->first_name;
 | 
			
		||||
        $title = $this->vacationRequest->name;
 | 
			
		||||
        $type = $this->vacationRequest->type->label();
 | 
			
		||||
        $status = $this->vacationRequest->state->label();
 | 
			
		||||
        $from = $this->vacationRequest->from->toDisplayString();
 | 
			
		||||
        $to = $this->vacationRequest->to->toDisplayString();
 | 
			
		||||
        $days = $this->vacationRequest->vacations()->count();
 | 
			
		||||
        $requester = $this->vacationRequest->user->profile->full_name;
 | 
			
		||||
 | 
			
		||||
        return (new MailMessage())
 | 
			
		||||
            ->greeting(__("Hi :user!", [
 | 
			
		||||
                "user" => $user,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->subject(__("Vacation request :title has been :status", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
                "status" => $status,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("The vacation request :title from user :requester has been :status.", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
                "requester" => $requester,
 | 
			
		||||
                "status" => $status,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->subject($this->buildSubject())
 | 
			
		||||
            ->line($this->buildDescription())
 | 
			
		||||
            ->line(__("Vacation type: :type", [
 | 
			
		||||
                "type" => $type,
 | 
			
		||||
            ]))
 | 
			
		||||
@@ -74,4 +74,21 @@ class VacationRequestStatusChangedNotification extends Notification
 | 
			
		||||
            ]))
 | 
			
		||||
            ->action(__("Click here for details"), $url);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function buildSubject(): string
 | 
			
		||||
    {
 | 
			
		||||
        return __("Vacation request :title has been :status", [
 | 
			
		||||
            "title" => $this->vacationRequest->name,
 | 
			
		||||
            "status" => $this->vacationRequest->state->label(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function buildDescription(): string
 | 
			
		||||
    {
 | 
			
		||||
        return __("The vacation request :title from user :requester has been :status.", [
 | 
			
		||||
            "title" => $this->vacationRequest->name,
 | 
			
		||||
            "requester" => $this->vacationRequest->user->profile->full_name,
 | 
			
		||||
            "status" => $this->vacationRequest->state->label(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,17 @@ class VacationRequestWaitsForApprovalNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    public function via(): array
 | 
			
		||||
    {
 | 
			
		||||
        return ["mail"];
 | 
			
		||||
        return [Channels::MAIL, Channels::SLACK];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toSlack(): string
 | 
			
		||||
    {
 | 
			
		||||
        $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]);
 | 
			
		||||
 | 
			
		||||
        return implode("\n", [
 | 
			
		||||
            $this->buildDescription(),
 | 
			
		||||
            "<${url}|Zobacz szczegóły>",
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class PolishHolidaysRetriever
 | 
			
		||||
 | 
			
		||||
    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]),
 | 
			
		||||
            "date" => Carbon::createFromTimestamp($holiday->getTimestamp()),
 | 
			
		||||
        ])->values();
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class TimesheetExport implements WithMultipleSheets
 | 
			
		||||
    public function sheets(): array
 | 
			
		||||
    {
 | 
			
		||||
        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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -193,8 +193,8 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With
 | 
			
		||||
            ->get()
 | 
			
		||||
            ->groupBy(
 | 
			
		||||
                [
 | 
			
		||||
                    fn(Vacation $vacation) => $vacation->date->toDateString(),
 | 
			
		||||
                    fn(Vacation $vacation) => $vacation->vacationRequest->type->value,
 | 
			
		||||
                    fn(Vacation $vacation): string => $vacation->date->toDateString(),
 | 
			
		||||
                    fn(Vacation $vacation): string => $vacation->vacationRequest->type->value,
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,8 @@ class UserVacationStatsRetriever
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->get()
 | 
			
		||||
            ->groupBy(fn(Vacation $vacation) => strtolower($vacation->date->englishMonth))
 | 
			
		||||
            ->map(fn(Collection $items) => $items->count());
 | 
			
		||||
            ->groupBy(fn(Vacation $vacation): string => strtolower($vacation->date->englishMonth))
 | 
			
		||||
            ->map(fn(Collection $items): int => $items->count());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int
 | 
			
		||||
@@ -107,13 +107,13 @@ class UserVacationStatsRetriever
 | 
			
		||||
    {
 | 
			
		||||
        $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
 | 
			
		||||
    {
 | 
			
		||||
        $types = VacationType::all();
 | 
			
		||||
 | 
			
		||||
        return $types->filter(fn(VacationType $type) => !$this->configRetriever->hasLimit($type));
 | 
			
		||||
        return $types->filter(fn(VacationType $type): bool => !$this->configRetriever->hasLimit($type));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,6 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
    {
 | 
			
		||||
        $types = VacationType::all();
 | 
			
		||||
 | 
			
		||||
        return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type));
 | 
			
		||||
        return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ class VacationTypeCanBeSelected implements VacationRequestRule
 | 
			
		||||
        $employmentForm = $vacationRequest->user->profile->employment_form;
 | 
			
		||||
 | 
			
		||||
        $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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ class YearPeriodRetriever
 | 
			
		||||
 | 
			
		||||
        $years = YearPeriod::all();
 | 
			
		||||
 | 
			
		||||
        $navigation = $years->map(fn(YearPeriod $yearPeriod) => $this->toNavigation($yearPeriod));
 | 
			
		||||
        $navigation = $years->map(fn(YearPeriod $yearPeriod): array => $this->toNavigation($yearPeriod));
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            "current" => $this->toNavigation($current),
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,17 @@ use Database\Factories\KeyFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
			
		||||
use Illuminate\Notifications\Notifiable;
 | 
			
		||||
use Toby\Domain\Notifications\Notifiable as NotifiableInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property User $user
 | 
			
		||||
 */
 | 
			
		||||
class Key extends Model
 | 
			
		||||
class Key extends Model implements NotifiableInterface
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
    use Notifiable;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
@@ -24,6 +27,11 @@ class Key extends Model
 | 
			
		||||
        return $this->belongsTo(User::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function routeNotificationForSlack(): string
 | 
			
		||||
    {
 | 
			
		||||
        return config("services.slack.default_channel");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static function newFactory(): KeyFactory
 | 
			
		||||
    {
 | 
			
		||||
        return KeyFactory::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ use Toby\Eloquent\Helpers\ColorGenerator;
 | 
			
		||||
 * @property string $position
 | 
			
		||||
 * @property EmploymentForm $employment_form
 | 
			
		||||
 * @property Carbon $employment_date
 | 
			
		||||
 * @property Carbon $birthday
 | 
			
		||||
 */
 | 
			
		||||
class Profile extends Model
 | 
			
		||||
{
 | 
			
		||||
@@ -30,6 +31,7 @@ class Profile extends Model
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        "employment_form" => EmploymentForm::class,
 | 
			
		||||
        "employment_date" => "date",
 | 
			
		||||
        "birthday" => "date",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function user(): BelongsTo
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ use Illuminate\Notifications\Notifiable;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\Enums\EmploymentForm;
 | 
			
		||||
use Toby\Domain\Enums\Role;
 | 
			
		||||
use Toby\Domain\Notifications\Notifiable as NotifiableInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
@@ -26,7 +27,7 @@ use Toby\Domain\Enums\Role;
 | 
			
		||||
 * @property Collection $vacationRequests
 | 
			
		||||
 * @property Collection $vacations
 | 
			
		||||
 */
 | 
			
		||||
class User extends Authenticatable
 | 
			
		||||
class User extends Authenticatable implements NotifiableInterface
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
    use Notifiable;
 | 
			
		||||
@@ -99,7 +100,7 @@ class User extends Authenticatable
 | 
			
		||||
            ->where("email", "ILIKE", "%{$text}%")
 | 
			
		||||
            ->orWhereRelation(
 | 
			
		||||
                "profile",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                fn(Builder $query): Builder => $query
 | 
			
		||||
                    ->where("first_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
 | 
			
		||||
    {
 | 
			
		||||
        return UserFactory::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -16,6 +16,6 @@ class CalculateVacationDaysController extends Controller
 | 
			
		||||
    {
 | 
			
		||||
        $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"));
 | 
			
		||||
 | 
			
		||||
        $types = VacationType::all()
 | 
			
		||||
            ->filter(fn(VacationType $type) => $configRetriever->isAvailableFor($type, $user->profile->employment_form))
 | 
			
		||||
            ->map(fn(VacationType $type) => [
 | 
			
		||||
            ->filter(fn(VacationType $type): bool => $configRetriever->isAvailableFor($type, $user->profile->employment_form))
 | 
			
		||||
            ->map(fn(VacationType $type): array => [
 | 
			
		||||
                "label" => $type->label(),
 | 
			
		||||
                "value" => $type->value,
 | 
			
		||||
            ])
 | 
			
		||||
 
 | 
			
		||||
@@ -7,12 +7,11 @@ namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\DailySummaryRetriever;
 | 
			
		||||
use Toby\Domain\UserVacationStatsRetriever;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Domain\VacationTypeConfigRetriever;
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\Vacation;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Infrastructure\Http\Resources\HolidayResource;
 | 
			
		||||
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
 | 
			
		||||
@@ -25,24 +24,14 @@ class DashboardController extends Controller
 | 
			
		||||
        YearPeriodRetriever $yearPeriodRetriever,
 | 
			
		||||
        UserVacationStatsRetriever $vacationStatsRetriever,
 | 
			
		||||
        VacationTypeConfigRetriever $configRetriever,
 | 
			
		||||
        DailySummaryRetriever $dailySummaryRetriever,
 | 
			
		||||
    ): Response {
 | 
			
		||||
        $user = $request->user();
 | 
			
		||||
        $now = Carbon::now();
 | 
			
		||||
        $yearPeriod = $yearPeriodRetriever->selected();
 | 
			
		||||
 | 
			
		||||
        $absences = Vacation::query()
 | 
			
		||||
            ->with(["user", "vacationRequest"])
 | 
			
		||||
            ->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();
 | 
			
		||||
        $absences = $dailySummaryRetriever->getAbsences($now);
 | 
			
		||||
        $remoteDays = $dailySummaryRetriever->getRemoteDays($now);
 | 
			
		||||
 | 
			
		||||
        if ($user->can("listAll", VacationRequest::class)) {
 | 
			
		||||
            $vacationRequests = $yearPeriod->vacationRequests()
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Symfony\Component\HttpFoundation\RedirectResponse;
 | 
			
		||||
use Toby\Domain\Notifications\KeyHasBeenGivenNotification;
 | 
			
		||||
use Toby\Domain\Notifications\KeyHasBeenTakenNotification;
 | 
			
		||||
use Toby\Eloquent\Models\Key;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Infrastructure\Http\Requests\GiveKeyRequest;
 | 
			
		||||
@@ -60,6 +62,8 @@ class KeysController extends Controller
 | 
			
		||||
 | 
			
		||||
        $key->save();
 | 
			
		||||
 | 
			
		||||
        $key->notify(new KeyHasBeenTakenNotification($request->user(), $previousUser));
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
            ->back()
 | 
			
		||||
            ->with("success", __("Key no :number has been taken from :user.", [
 | 
			
		||||
@@ -81,6 +85,8 @@ class KeysController extends Controller
 | 
			
		||||
 | 
			
		||||
        $key->save();
 | 
			
		||||
 | 
			
		||||
        $key->notify(new KeyHasBeenGivenNotification($request->user(), $recipient));
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
            ->back()
 | 
			
		||||
            ->with("success", __("Key no :number has been given to :user.", [
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,10 @@ class TimesheetController extends Controller
 | 
			
		||||
 | 
			
		||||
        $types = VacationType::all()
 | 
			
		||||
            ->filter(
 | 
			
		||||
                fn(VacationType $type) => $configRetriever->isAvailableFor($type, EmploymentForm::EmploymentContract)
 | 
			
		||||
                    && $configRetriever->isVacation($type),
 | 
			
		||||
                fn(VacationType $type): bool => $configRetriever->isAvailableFor(
 | 
			
		||||
                    $type,
 | 
			
		||||
                    EmploymentForm::EmploymentContract,
 | 
			
		||||
                ) && $configRetriever->isVacation($type),
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        $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}")
 | 
			
		||||
            ->values();
 | 
			
		||||
 | 
			
		||||
        $limitsResource = $limits->map(fn(VacationLimit $limit) => [
 | 
			
		||||
        $limitsResource = $limits->map(fn(VacationLimit $limit): array => [
 | 
			
		||||
            "id" => $limit->id,
 | 
			
		||||
            "user" => new UserResource($limit->user),
 | 
			
		||||
            "hasVacation" => $limit->hasVacation(),
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ class HandleInertiaRequests extends Middleware
 | 
			
		||||
    {
 | 
			
		||||
        $user = $request->user();
 | 
			
		||||
 | 
			
		||||
        return fn() => [
 | 
			
		||||
        return fn(): array => [
 | 
			
		||||
            "user" => $user ? new UserResource($user) : null,
 | 
			
		||||
            "can" => [
 | 
			
		||||
                "manageVacationLimits" => $user ? $user->can("manageVacationLimits") : false,
 | 
			
		||||
@@ -45,7 +45,7 @@ class HandleInertiaRequests extends Middleware
 | 
			
		||||
 | 
			
		||||
    protected function getFlashData(Request $request): Closure
 | 
			
		||||
    {
 | 
			
		||||
        return fn() => [
 | 
			
		||||
        return fn(): array => [
 | 
			
		||||
            "success" => $request->session()->get("success"),
 | 
			
		||||
            "error" => $request->session()->get("error"),
 | 
			
		||||
            "info" => $request->session()->get("info"),
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,8 @@ class UserRequest extends FormRequest
 | 
			
		||||
            "position" => ["required"],
 | 
			
		||||
            "employmentForm" => ["required", new Enum(EmploymentForm::class)],
 | 
			
		||||
            "employmentDate" => ["required", "date_format:Y-m-d"],
 | 
			
		||||
            "birthday" => ["nullable", "date_format:Y-m-d"],
 | 
			
		||||
            "slackId" => [],
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -41,6 +43,8 @@ class UserRequest extends FormRequest
 | 
			
		||||
            "position" => $this->get("position"),
 | 
			
		||||
            "employment_form" => $this->get("employmentForm"),
 | 
			
		||||
            "employment_date" => $this->get("employmentDate"),
 | 
			
		||||
            "birthday" => $this->get("birthday"),
 | 
			
		||||
            "slack_id" => $this->get("slackId"),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,8 @@ class UserFormDataResource extends JsonResource
 | 
			
		||||
            "position" => $this->profile->position,
 | 
			
		||||
            "employmentForm" => $this->profile->employment_form,
 | 
			
		||||
            "employmentDate" => $this->profile->employment_date->toDateString(),
 | 
			
		||||
            "birthday" => $this->profile->birthday?->toDateString(),
 | 
			
		||||
            "slackId" => $this->profile->slack_id,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								app/Infrastructure/Slack/Channels/SlackApiChannel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								app/Infrastructure/Slack/Channels/SlackApiChannel.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
<?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);
 | 
			
		||||
 | 
			
		||||
        return Http::withToken($this->getClientToken())
 | 
			
		||||
            ->post($url, [
 | 
			
		||||
                "channel" => $channel,
 | 
			
		||||
                "text" => $notification->toSlack($notifiable),
 | 
			
		||||
            ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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:");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -21,7 +21,8 @@
 | 
			
		||||
        "maatwebsite/excel": "^3.1",
 | 
			
		||||
        "rackbeat/laravel-ui-avatars": "^1.0",
 | 
			
		||||
        "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": {
 | 
			
		||||
        "blumilksoftware/codestyle": "^1.0.0",
 | 
			
		||||
@@ -61,7 +62,8 @@
 | 
			
		||||
    "extra": {
 | 
			
		||||
        "laravel": {
 | 
			
		||||
            "dont-discover": [
 | 
			
		||||
                "laravel/telescope"
 | 
			
		||||
                "laravel/telescope",
 | 
			
		||||
                "spatie/laravel-slack-slash-command"
 | 
			
		||||
            ]
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										383
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										383
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
 | 
			
		||||
        "This file is @generated automatically"
 | 
			
		||||
    ],
 | 
			
		||||
    "content-hash": "414a1fc13e0731e59605248bd4e39de6",
 | 
			
		||||
    "content-hash": "24a1b3a5dd7c4d4f50d521dda4b6654e",
 | 
			
		||||
    "packages": [
 | 
			
		||||
        {
 | 
			
		||||
            "name": "asm89/stack-cors",
 | 
			
		||||
@@ -815,23 +815,23 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "firebase/php-jwt",
 | 
			
		||||
            "version": "v5.5.1",
 | 
			
		||||
            "version": "v6.1.2",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/firebase/php-jwt.git",
 | 
			
		||||
                "reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
 | 
			
		||||
                "reference": "c297139da7c6873dbd67cbd1093f09ec0bbd0c50"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
 | 
			
		||||
                "reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
 | 
			
		||||
                "url": "https://api.github.com/repos/firebase/php-jwt/zipball/c297139da7c6873dbd67cbd1093f09ec0bbd0c50",
 | 
			
		||||
                "reference": "c297139da7c6873dbd67cbd1093f09ec0bbd0c50",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": ">=5.3.0"
 | 
			
		||||
                "php": "^7.1||^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "phpunit/phpunit": ">=4.8 <=9"
 | 
			
		||||
                "phpunit/phpunit": "^7.5||9.5"
 | 
			
		||||
            },
 | 
			
		||||
            "suggest": {
 | 
			
		||||
                "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
 | 
			
		||||
@@ -866,9 +866,9 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/firebase/php-jwt/issues",
 | 
			
		||||
                "source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
 | 
			
		||||
                "source": "https://github.com/firebase/php-jwt/tree/v6.1.2"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2021-11-08T20:18:51+00:00"
 | 
			
		||||
            "time": "2022-04-21T14:37:18+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "fruitcake/laravel-cors",
 | 
			
		||||
@@ -1022,16 +1022,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "google/apiclient",
 | 
			
		||||
            "version": "v2.12.2",
 | 
			
		||||
            "version": "v2.12.4",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/googleapis/google-api-php-client.git",
 | 
			
		||||
                "reference": "a18b0e1ef5618523c607c01a41ec137c7f9af3b1"
 | 
			
		||||
                "reference": "702eed9ae7022ba20dc7118c8161060cb50ee9f8"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/a18b0e1ef5618523c607c01a41ec137c7f9af3b1",
 | 
			
		||||
                "reference": "a18b0e1ef5618523c607c01a41ec137c7f9af3b1",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/702eed9ae7022ba20dc7118c8161060cb50ee9f8",
 | 
			
		||||
                "reference": "702eed9ae7022ba20dc7118c8161060cb50ee9f8",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -1087,22 +1087,22 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/googleapis/google-api-php-client/issues",
 | 
			
		||||
                "source": "https://github.com/googleapis/google-api-php-client/tree/v2.12.2"
 | 
			
		||||
                "source": "https://github.com/googleapis/google-api-php-client/tree/v2.12.4"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-04-05T16:19:05+00:00"
 | 
			
		||||
            "time": "2022-04-20T16:44:03+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "google/apiclient-services",
 | 
			
		||||
            "version": "v0.242.0",
 | 
			
		||||
            "version": "v0.246.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/googleapis/google-api-php-client-services.git",
 | 
			
		||||
                "reference": "73d4c0ed4b241e7396699e0ee1d1cdebabac25e8"
 | 
			
		||||
                "reference": "33aef1ccce34799a1124c39951fed8ad0b16aced"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/73d4c0ed4b241e7396699e0ee1d1cdebabac25e8",
 | 
			
		||||
                "reference": "73d4c0ed4b241e7396699e0ee1d1cdebabac25e8",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/33aef1ccce34799a1124c39951fed8ad0b16aced",
 | 
			
		||||
                "reference": "33aef1ccce34799a1124c39951fed8ad0b16aced",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -1131,30 +1131,30 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/googleapis/google-api-php-client-services/issues",
 | 
			
		||||
                "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.242.0"
 | 
			
		||||
                "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.246.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-04-03T01:24:10+00:00"
 | 
			
		||||
            "time": "2022-04-24T00:58:37+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "google/auth",
 | 
			
		||||
            "version": "v1.19.0",
 | 
			
		||||
            "version": "v1.21.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/googleapis/google-auth-library-php.git",
 | 
			
		||||
                "reference": "31e5d24d5fa0eaf6adc7e596292dc4732f4b60c5"
 | 
			
		||||
                "reference": "73392bad2eb6852eea9084b6bbdec752515cb849"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/31e5d24d5fa0eaf6adc7e596292dc4732f4b60c5",
 | 
			
		||||
                "reference": "31e5d24d5fa0eaf6adc7e596292dc4732f4b60c5",
 | 
			
		||||
                "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/73392bad2eb6852eea9084b6bbdec752515cb849",
 | 
			
		||||
                "reference": "73392bad2eb6852eea9084b6bbdec752515cb849",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "firebase/php-jwt": "~5.0",
 | 
			
		||||
                "firebase/php-jwt": "^5.5||^6.0",
 | 
			
		||||
                "guzzlehttp/guzzle": "^6.2.1|^7.0",
 | 
			
		||||
                "guzzlehttp/psr7": "^1.7|^2.0",
 | 
			
		||||
                "php": ">=5.6",
 | 
			
		||||
                "psr/cache": "^1.0|^2.0",
 | 
			
		||||
                "php": "^7.1||^8.0",
 | 
			
		||||
                "psr/cache": "^1.0|^2.0|^3.0",
 | 
			
		||||
                "psr/http-message": "^1.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
@@ -1162,7 +1162,7 @@
 | 
			
		||||
                "kelvinmo/simplejwt": "^0.2.5|^0.5.1",
 | 
			
		||||
                "phpseclib/phpseclib": "^2.0.31",
 | 
			
		||||
                "phpspec/prophecy-phpunit": "^1.1",
 | 
			
		||||
                "phpunit/phpunit": "^5.7||^8.5.13",
 | 
			
		||||
                "phpunit/phpunit": "^7.5||^8.5",
 | 
			
		||||
                "sebastian/comparator": ">=1.2.3",
 | 
			
		||||
                "squizlabs/php_codesniffer": "^3.5"
 | 
			
		||||
            },
 | 
			
		||||
@@ -1189,9 +1189,9 @@
 | 
			
		||||
            "support": {
 | 
			
		||||
                "docs": "https://googleapis.github.io/google-auth-library-php/main/",
 | 
			
		||||
                "issues": "https://github.com/googleapis/google-auth-library-php/issues",
 | 
			
		||||
                "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.19.0"
 | 
			
		||||
                "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.21.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-03-24T21:22:45+00:00"
 | 
			
		||||
            "time": "2022-04-13T20:35:52+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "graham-campbell/result-type",
 | 
			
		||||
@@ -1733,16 +1733,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/framework",
 | 
			
		||||
            "version": "v9.7.0",
 | 
			
		||||
            "version": "v9.9.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/laravel/framework.git",
 | 
			
		||||
                "reference": "54c9696ee3e558ab29317ed6e0cb16bb9db5aad4"
 | 
			
		||||
                "reference": "4d5a07640891b772188d7737348886a0222737d8"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/framework/zipball/54c9696ee3e558ab29317ed6e0cb16bb9db5aad4",
 | 
			
		||||
                "reference": "54c9696ee3e558ab29317ed6e0cb16bb9db5aad4",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/framework/zipball/4d5a07640891b772188d7737348886a0222737d8",
 | 
			
		||||
                "reference": "4d5a07640891b772188d7737348886a0222737d8",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -1908,20 +1908,76 @@
 | 
			
		||||
                "issues": "https://github.com/laravel/framework/issues",
 | 
			
		||||
                "source": "https://github.com/laravel/framework"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-04-05T15:07:51+00:00"
 | 
			
		||||
            "time": "2022-04-19T15:01:23+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/sanctum",
 | 
			
		||||
            "version": "v2.15.0",
 | 
			
		||||
            "name": "laravel/helpers",
 | 
			
		||||
            "version": "v1.5.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/laravel/sanctum.git",
 | 
			
		||||
                "reference": "5be160413b6f37dcf8758663edeab12d0e806f56"
 | 
			
		||||
                "url": "https://github.com/laravel/helpers.git",
 | 
			
		||||
                "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/sanctum/zipball/5be160413b6f37dcf8758663edeab12d0e806f56",
 | 
			
		||||
                "reference": "5be160413b6f37dcf8758663edeab12d0e806f56",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/helpers/zipball/c28b0ccd799d58564c41a62395ac9511a1e72931",
 | 
			
		||||
                "reference": "c28b0ccd799d58564c41a62395ac9511a1e72931",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0",
 | 
			
		||||
                "php": "^7.1.3|^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "phpunit/phpunit": "^7.0|^8.0|^9.0"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-master": "1.x-dev"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "files": [
 | 
			
		||||
                    "src/helpers.php"
 | 
			
		||||
                ]
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Taylor Otwell",
 | 
			
		||||
                    "email": "taylor@laravel.com"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Dries Vints",
 | 
			
		||||
                    "email": "dries@laravel.com"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Provides backwards compatibility for helpers in the latest Laravel release.",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "helpers",
 | 
			
		||||
                "laravel"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/laravel/helpers/tree/v1.5.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-01-12T15:58:51+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/sanctum",
 | 
			
		||||
            "version": "v2.15.1",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/laravel/sanctum.git",
 | 
			
		||||
                "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/sanctum/zipball/31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473",
 | 
			
		||||
                "reference": "31fbe6f85aee080c4dc2f9b03dc6dd5d0ee72473",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -1973,7 +2029,7 @@
 | 
			
		||||
                "issues": "https://github.com/laravel/sanctum/issues",
 | 
			
		||||
                "source": "https://github.com/laravel/sanctum"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-03-28T13:53:05+00:00"
 | 
			
		||||
            "time": "2022-04-08T13:39:49+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/serializable-closure",
 | 
			
		||||
@@ -2105,16 +2161,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/telescope",
 | 
			
		||||
            "version": "v4.8.2",
 | 
			
		||||
            "version": "v4.8.3",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/laravel/telescope.git",
 | 
			
		||||
                "reference": "88e3ded3e1fc301a82403820b2f68ec5a9e092e3"
 | 
			
		||||
                "reference": "bb23d58161032c8745d38348452afcbcd8adfc78"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/telescope/zipball/88e3ded3e1fc301a82403820b2f68ec5a9e092e3",
 | 
			
		||||
                "reference": "88e3ded3e1fc301a82403820b2f68ec5a9e092e3",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/telescope/zipball/bb23d58161032c8745d38348452afcbcd8adfc78",
 | 
			
		||||
                "reference": "bb23d58161032c8745d38348452afcbcd8adfc78",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2167,9 +2223,9 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/laravel/telescope/issues",
 | 
			
		||||
                "source": "https://github.com/laravel/telescope/tree/v4.8.2"
 | 
			
		||||
                "source": "https://github.com/laravel/telescope/tree/v4.8.3"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-04-01T14:27:12+00:00"
 | 
			
		||||
            "time": "2022-04-11T14:29:02+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/tinker",
 | 
			
		||||
@@ -2404,16 +2460,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "league/commonmark",
 | 
			
		||||
            "version": "2.2.3",
 | 
			
		||||
            "version": "2.3.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/thephpleague/commonmark.git",
 | 
			
		||||
                "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71"
 | 
			
		||||
                "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
 | 
			
		||||
                "reference": "47b015bc4e50fd4438c1ffef6139a1fb65d2ab71",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/32a49eb2b38fe5e5c417ab748a45d0beaab97955",
 | 
			
		||||
                "reference": "32a49eb2b38fe5e5c417ab748a45d0beaab97955",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2422,17 +2478,19 @@
 | 
			
		||||
                "php": "^7.4 || ^8.0",
 | 
			
		||||
                "psr/event-dispatcher": "^1.0",
 | 
			
		||||
                "symfony/deprecation-contracts": "^2.1 || ^3.0",
 | 
			
		||||
                "symfony/polyfill-php80": "^1.15"
 | 
			
		||||
                "symfony/polyfill-php80": "^1.16"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "cebe/markdown": "^1.0",
 | 
			
		||||
                "commonmark/cmark": "0.30.0",
 | 
			
		||||
                "commonmark/commonmark.js": "0.30.0",
 | 
			
		||||
                "composer/package-versions-deprecated": "^1.8",
 | 
			
		||||
                "embed/embed": "^4.4",
 | 
			
		||||
                "erusev/parsedown": "^1.0",
 | 
			
		||||
                "ext-json": "*",
 | 
			
		||||
                "github/gfm": "0.29.0",
 | 
			
		||||
                "michelf/php-markdown": "^1.4",
 | 
			
		||||
                "nyholm/psr7": "^1.5",
 | 
			
		||||
                "phpstan/phpstan": "^0.12.88 || ^1.0.0",
 | 
			
		||||
                "phpunit/phpunit": "^9.5.5",
 | 
			
		||||
                "scrutinizer/ocular": "^1.8.1",
 | 
			
		||||
@@ -2447,7 +2505,7 @@
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-main": "2.3-dev"
 | 
			
		||||
                    "dev-main": "2.4-dev"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
@@ -2504,7 +2562,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-02-26T21:24:45+00:00"
 | 
			
		||||
            "time": "2022-04-07T22:37:05+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "league/config",
 | 
			
		||||
@@ -2590,16 +2648,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "league/flysystem",
 | 
			
		||||
            "version": "3.0.14",
 | 
			
		||||
            "version": "3.0.17",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/thephpleague/flysystem.git",
 | 
			
		||||
                "reference": "46a5450352540e89cb8e7eb20c58b5b4aae481f6"
 | 
			
		||||
                "reference": "29eb78cac0be0c22237c5e0f6f98234d97037d79"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/46a5450352540e89cb8e7eb20c58b5b4aae481f6",
 | 
			
		||||
                "reference": "46a5450352540e89cb8e7eb20c58b5b4aae481f6",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/29eb78cac0be0c22237c5e0f6f98234d97037d79",
 | 
			
		||||
                "reference": "29eb78cac0be0c22237c5e0f6f98234d97037d79",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2660,7 +2718,7 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/thephpleague/flysystem/issues",
 | 
			
		||||
                "source": "https://github.com/thephpleague/flysystem/tree/3.0.14"
 | 
			
		||||
                "source": "https://github.com/thephpleague/flysystem/tree/3.0.17"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -2676,20 +2734,20 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-04-06T18:17:37+00:00"
 | 
			
		||||
            "time": "2022-04-14T14:57:13+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "league/mime-type-detection",
 | 
			
		||||
            "version": "1.9.0",
 | 
			
		||||
            "version": "1.11.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/thephpleague/mime-type-detection.git",
 | 
			
		||||
                "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69"
 | 
			
		||||
                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/aa70e813a6ad3d1558fc927863d47309b4c23e69",
 | 
			
		||||
                "reference": "aa70e813a6ad3d1558fc927863d47309b4c23e69",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
 | 
			
		||||
                "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2720,7 +2778,7 @@
 | 
			
		||||
            "description": "Mime-type detection for Flysystem",
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/thephpleague/mime-type-detection/issues",
 | 
			
		||||
                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.9.0"
 | 
			
		||||
                "source": "https://github.com/thephpleague/mime-type-detection/tree/1.11.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -2732,20 +2790,20 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2021-11-21T11:48:40+00:00"
 | 
			
		||||
            "time": "2022-04-17T13:12:02+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "league/oauth1-client",
 | 
			
		||||
            "version": "v1.10.0",
 | 
			
		||||
            "version": "v1.10.1",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/thephpleague/oauth1-client.git",
 | 
			
		||||
                "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc"
 | 
			
		||||
                "reference": "d6365b901b5c287dd41f143033315e2f777e1167"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
 | 
			
		||||
                "reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
 | 
			
		||||
                "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167",
 | 
			
		||||
                "reference": "d6365b901b5c287dd41f143033315e2f777e1167",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2806,22 +2864,22 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/thephpleague/oauth1-client/issues",
 | 
			
		||||
                "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.0"
 | 
			
		||||
                "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2021-08-15T23:05:49+00:00"
 | 
			
		||||
            "time": "2022-04-15T14:02:14+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "maatwebsite/excel",
 | 
			
		||||
            "version": "3.1.38",
 | 
			
		||||
            "version": "3.1.39",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/SpartnerNL/Laravel-Excel.git",
 | 
			
		||||
                "reference": "dff132ce4d30b19863f4e84de1613fca99604992"
 | 
			
		||||
                "reference": "5165334de44c6f7788a5818a1d019aa71a43e092"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/dff132ce4d30b19863f4e84de1613fca99604992",
 | 
			
		||||
                "reference": "dff132ce4d30b19863f4e84de1613fca99604992",
 | 
			
		||||
                "url": "https://api.github.com/repos/SpartnerNL/Laravel-Excel/zipball/5165334de44c6f7788a5818a1d019aa71a43e092",
 | 
			
		||||
                "reference": "5165334de44c6f7788a5818a1d019aa71a43e092",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -2874,7 +2932,7 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/SpartnerNL/Laravel-Excel/issues",
 | 
			
		||||
                "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.38"
 | 
			
		||||
                "source": "https://github.com/SpartnerNL/Laravel-Excel/tree/3.1.39"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -2886,7 +2944,7 @@
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-03-24T16:00:29+00:00"
 | 
			
		||||
            "time": "2022-04-23T11:44:18+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "maennchen/zipstream-php",
 | 
			
		||||
@@ -3118,16 +3176,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "monolog/monolog",
 | 
			
		||||
            "version": "2.4.0",
 | 
			
		||||
            "version": "2.5.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/Seldaek/monolog.git",
 | 
			
		||||
                "reference": "d7fd7450628561ba697b7097d86db72662f54aef"
 | 
			
		||||
                "reference": "4192345e260f1d51b365536199744b987e160edc"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/d7fd7450628561ba697b7097d86db72662f54aef",
 | 
			
		||||
                "reference": "d7fd7450628561ba697b7097d86db72662f54aef",
 | 
			
		||||
                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4192345e260f1d51b365536199744b987e160edc",
 | 
			
		||||
                "reference": "4192345e260f1d51b365536199744b987e160edc",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -3201,7 +3259,7 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/Seldaek/monolog/issues",
 | 
			
		||||
                "source": "https://github.com/Seldaek/monolog/tree/2.4.0"
 | 
			
		||||
                "source": "https://github.com/Seldaek/monolog/tree/2.5.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -3213,7 +3271,7 @@
 | 
			
		||||
                    "type": "tidelift"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-03-14T12:44:37+00:00"
 | 
			
		||||
            "time": "2022-04-08T15:43:54+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "myclabs/php-enum",
 | 
			
		||||
@@ -3856,16 +3914,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "phpoffice/phpspreadsheet",
 | 
			
		||||
            "version": "1.22.0",
 | 
			
		||||
            "version": "1.23.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
 | 
			
		||||
                "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48"
 | 
			
		||||
                "reference": "21e4cf62699eebf007db28775f7d1554e612ed9e"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/3a9e29b4f386a08a151a33578e80ef1747037a48",
 | 
			
		||||
                "reference": "3a9e29b4f386a08a151a33578e80ef1747037a48",
 | 
			
		||||
                "url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/21e4cf62699eebf007db28775f7d1554e612ed9e",
 | 
			
		||||
                "reference": "21e4cf62699eebf007db28775f7d1554e612ed9e",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -3889,7 +3947,7 @@
 | 
			
		||||
                "php": "^7.3 || ^8.0",
 | 
			
		||||
                "psr/http-client": "^1.0",
 | 
			
		||||
                "psr/http-factory": "^1.0",
 | 
			
		||||
                "psr/simple-cache": "^1.0"
 | 
			
		||||
                "psr/simple-cache": "^1.0 || ^2.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "dealerdirect/phpcodesniffer-composer-installer": "dev-master",
 | 
			
		||||
@@ -3954,9 +4012,9 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
 | 
			
		||||
                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.22.0"
 | 
			
		||||
                "source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.23.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-02-18T12:57:07+00:00"
 | 
			
		||||
            "time": "2022-04-24T13:53:10+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "phpoption/phpoption",
 | 
			
		||||
@@ -4140,16 +4198,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "psr/cache",
 | 
			
		||||
            "version": "2.0.0",
 | 
			
		||||
            "version": "3.0.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/php-fig/cache.git",
 | 
			
		||||
                "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b"
 | 
			
		||||
                "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/php-fig/cache/zipball/213f9dbc5b9bfbc4f8db86d2838dc968752ce13b",
 | 
			
		||||
                "reference": "213f9dbc5b9bfbc4f8db86d2838dc968752ce13b",
 | 
			
		||||
                "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
 | 
			
		||||
                "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -4183,9 +4241,9 @@
 | 
			
		||||
                "psr-6"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/php-fig/cache/tree/2.0.0"
 | 
			
		||||
                "source": "https://github.com/php-fig/cache/tree/3.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2021-02-03T23:23:37+00:00"
 | 
			
		||||
            "time": "2021-02-03T23:26:27+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "psr/container",
 | 
			
		||||
@@ -4502,25 +4560,25 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "psr/simple-cache",
 | 
			
		||||
            "version": "1.0.1",
 | 
			
		||||
            "version": "2.0.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/php-fig/simple-cache.git",
 | 
			
		||||
                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b"
 | 
			
		||||
                "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
 | 
			
		||||
                "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b",
 | 
			
		||||
                "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/8707bf3cea6f710bf6ef05491234e3ab06f6432a",
 | 
			
		||||
                "reference": "8707bf3cea6f710bf6ef05491234e3ab06f6432a",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "php": ">=5.3.0"
 | 
			
		||||
                "php": ">=8.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "branch-alias": {
 | 
			
		||||
                    "dev-master": "1.0.x-dev"
 | 
			
		||||
                    "dev-master": "2.0.x-dev"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
@@ -4535,7 +4593,7 @@
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "PHP-FIG",
 | 
			
		||||
                    "homepage": "http://www.php-fig.org/"
 | 
			
		||||
                    "homepage": "https://www.php-fig.org/"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Common interfaces for simple caching",
 | 
			
		||||
@@ -4547,9 +4605,9 @@
 | 
			
		||||
                "simple-cache"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/php-fig/simple-cache/tree/master"
 | 
			
		||||
                "source": "https://github.com/php-fig/simple-cache/tree/2.0.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2017-10-23T01:57:42+00:00"
 | 
			
		||||
            "time": "2021-10-29T13:22:09+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "psy/psysh",
 | 
			
		||||
@@ -5040,16 +5098,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "spatie/laravel-model-states",
 | 
			
		||||
            "version": "2.2.0",
 | 
			
		||||
            "version": "2.3.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/spatie/laravel-model-states.git",
 | 
			
		||||
                "reference": "7b31a63c0bd8b33d7dc5e12e6b16d2535d9b31a8"
 | 
			
		||||
                "reference": "bfa12486558952eca4d6c81d4dd803b83f065297"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/laravel-model-states/zipball/7b31a63c0bd8b33d7dc5e12e6b16d2535d9b31a8",
 | 
			
		||||
                "reference": "7b31a63c0bd8b33d7dc5e12e6b16d2535d9b31a8",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/laravel-model-states/zipball/bfa12486558952eca4d6c81d4dd803b83f065297",
 | 
			
		||||
                "reference": "bfa12486558952eca4d6c81d4dd803b83f065297",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -5098,7 +5156,7 @@
 | 
			
		||||
                "state"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "source": "https://github.com/spatie/laravel-model-states/tree/2.2.0"
 | 
			
		||||
                "source": "https://github.com/spatie/laravel-model-states/tree/2.3.0"
 | 
			
		||||
            },
 | 
			
		||||
            "funding": [
 | 
			
		||||
                {
 | 
			
		||||
@@ -5110,7 +5168,7 @@
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-03-03T11:22:16+00:00"
 | 
			
		||||
            "time": "2022-04-21T12:09:37+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "spatie/laravel-package-tools",
 | 
			
		||||
@@ -5171,6 +5229,71 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-03-15T20:01:36+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "spatie/laravel-slack-slash-command",
 | 
			
		||||
            "version": "1.11.3",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/spatie/laravel-slack-slash-command.git",
 | 
			
		||||
                "reference": "8e507653054ff08581b28d6ddf5bb4ce8c2e2335"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/laravel-slack-slash-command/zipball/8e507653054ff08581b28d6ddf5bb4ce8c2e2335",
 | 
			
		||||
                "reference": "8e507653054ff08581b28d6ddf5bb4ce8c2e2335",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
                "guzzlehttp/guzzle": "^7.0",
 | 
			
		||||
                "illuminate/config": "~5.8.0|^6.0|^7.0|^8.0|^9.0",
 | 
			
		||||
                "illuminate/queue": "~5.8.0|^6.0|^7.0|^8.0|^9.0",
 | 
			
		||||
                "illuminate/routing": "~5.8.0|^6.0|^7.0|^8.0|^9.0",
 | 
			
		||||
                "illuminate/support": "~5.8.0|^6.0|^7.0|^8.0|^9.0",
 | 
			
		||||
                "laravel/helpers": "^1.0",
 | 
			
		||||
                "php": "^7.3|^8.0"
 | 
			
		||||
            },
 | 
			
		||||
            "require-dev": {
 | 
			
		||||
                "mockery/mockery": "^1.0",
 | 
			
		||||
                "orchestra/testbench": "~3.8.0|^4.0|^5.0|^6.0|^7.0",
 | 
			
		||||
                "phpunit/phpunit": "^9.2"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "library",
 | 
			
		||||
            "extra": {
 | 
			
		||||
                "laravel": {
 | 
			
		||||
                    "providers": [
 | 
			
		||||
                        "Spatie\\SlashCommand\\SlashCommandServiceProvider"
 | 
			
		||||
                    ]
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "autoload": {
 | 
			
		||||
                "psr-4": {
 | 
			
		||||
                    "Spatie\\SlashCommand\\": "src"
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            "notification-url": "https://packagist.org/downloads/",
 | 
			
		||||
            "license": [
 | 
			
		||||
                "MIT"
 | 
			
		||||
            ],
 | 
			
		||||
            "authors": [
 | 
			
		||||
                {
 | 
			
		||||
                    "name": "Freek Van der Herten",
 | 
			
		||||
                    "email": "freek@spatie.be",
 | 
			
		||||
                    "homepage": "https://spatie.be",
 | 
			
		||||
                    "role": "Developer"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "description": "Make a Laravel app respond to a slash command from Slack",
 | 
			
		||||
            "homepage": "https://github.com/spatie/laravel-slack-slash-command",
 | 
			
		||||
            "keywords": [
 | 
			
		||||
                "laravel-slack",
 | 
			
		||||
                "spatie"
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/spatie/laravel-slack-slash-command/issues",
 | 
			
		||||
                "source": "https://github.com/spatie/laravel-slack-slash-command/tree/1.11.3"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-02-09T07:58:01+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/console",
 | 
			
		||||
            "version": "v6.0.7",
 | 
			
		||||
@@ -8265,16 +8388,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "laravel/dusk",
 | 
			
		||||
            "version": "v6.22.3",
 | 
			
		||||
            "version": "v6.23.0",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/laravel/dusk.git",
 | 
			
		||||
                "reference": "f42844d5a7eace45d199d276bb2bc23fec788256"
 | 
			
		||||
                "reference": "98901d49176977c96330fd8c2ca5460eee50a246"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/dusk/zipball/f42844d5a7eace45d199d276bb2bc23fec788256",
 | 
			
		||||
                "reference": "f42844d5a7eace45d199d276bb2bc23fec788256",
 | 
			
		||||
                "url": "https://api.github.com/repos/laravel/dusk/zipball/98901d49176977c96330fd8c2ca5460eee50a246",
 | 
			
		||||
                "reference": "98901d49176977c96330fd8c2ca5460eee50a246",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -8332,9 +8455,9 @@
 | 
			
		||||
            ],
 | 
			
		||||
            "support": {
 | 
			
		||||
                "issues": "https://github.com/laravel/dusk/issues",
 | 
			
		||||
                "source": "https://github.com/laravel/dusk/tree/v6.22.3"
 | 
			
		||||
                "source": "https://github.com/laravel/dusk/tree/v6.23.0"
 | 
			
		||||
            },
 | 
			
		||||
            "time": "2022-04-04T15:12:34+00:00"
 | 
			
		||||
            "time": "2022-04-11T18:55:12+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "mockery/mockery",
 | 
			
		||||
@@ -10522,16 +10645,16 @@
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "spatie/ignition",
 | 
			
		||||
            "version": "1.2.7",
 | 
			
		||||
            "version": "1.2.9",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/spatie/ignition.git",
 | 
			
		||||
                "reference": "2f059cf42b48f7c522efbba1c05ad59fc2c1a3f2"
 | 
			
		||||
                "reference": "db25202fab2d5c14613b8914a1bb374998bbf870"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/ignition/zipball/2f059cf42b48f7c522efbba1c05ad59fc2c1a3f2",
 | 
			
		||||
                "reference": "2f059cf42b48f7c522efbba1c05ad59fc2c1a3f2",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/ignition/zipball/db25202fab2d5c14613b8914a1bb374998bbf870",
 | 
			
		||||
                "reference": "db25202fab2d5c14613b8914a1bb374998bbf870",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -10588,20 +10711,20 @@
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-03-29T08:48:34+00:00"
 | 
			
		||||
            "time": "2022-04-23T20:37:21+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "spatie/laravel-ignition",
 | 
			
		||||
            "version": "1.2.0",
 | 
			
		||||
            "version": "1.2.2",
 | 
			
		||||
            "source": {
 | 
			
		||||
                "type": "git",
 | 
			
		||||
                "url": "https://github.com/spatie/laravel-ignition.git",
 | 
			
		||||
                "reference": "2b54c8c66f2d280f25e15064ebe3d5e3eda19820"
 | 
			
		||||
                "reference": "924d1ae878874ad0bb49f63b69a9af759a34ee78"
 | 
			
		||||
            },
 | 
			
		||||
            "dist": {
 | 
			
		||||
                "type": "zip",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/2b54c8c66f2d280f25e15064ebe3d5e3eda19820",
 | 
			
		||||
                "reference": "2b54c8c66f2d280f25e15064ebe3d5e3eda19820",
 | 
			
		||||
                "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/924d1ae878874ad0bb49f63b69a9af759a34ee78",
 | 
			
		||||
                "reference": "924d1ae878874ad0bb49f63b69a9af759a34ee78",
 | 
			
		||||
                "shasum": ""
 | 
			
		||||
            },
 | 
			
		||||
            "require": {
 | 
			
		||||
@@ -10678,7 +10801,7 @@
 | 
			
		||||
                    "type": "github"
 | 
			
		||||
                }
 | 
			
		||||
            ],
 | 
			
		||||
            "time": "2022-04-01T21:01:58+00:00"
 | 
			
		||||
            "time": "2022-04-14T18:04:51+00:00"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "name": "symfony/filesystem",
 | 
			
		||||
 
 | 
			
		||||
@@ -37,12 +37,12 @@ return [
 | 
			
		||||
        Illuminate\Translation\TranslationServiceProvider::class,
 | 
			
		||||
        Illuminate\Validation\ValidationServiceProvider::class,
 | 
			
		||||
        Illuminate\View\ViewServiceProvider::class,
 | 
			
		||||
        Barryvdh\DomPDF\ServiceProvider::class,
 | 
			
		||||
        Toby\Architecture\Providers\AppServiceProvider::class,
 | 
			
		||||
        Toby\Architecture\Providers\AuthServiceProvider::class,
 | 
			
		||||
        Toby\Architecture\Providers\EventServiceProvider::class,
 | 
			
		||||
        Toby\Architecture\Providers\RouteServiceProvider::class,
 | 
			
		||||
        Toby\Architecture\Providers\TelescopeServiceProvider::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"),
 | 
			
		||||
        "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()),
 | 
			
		||||
            "position" => $this->faker->jobTitle(),
 | 
			
		||||
            "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(),
 | 
			
		||||
            "birthday" => Carbon::createFromInterface($this->faker->dateTimeBetween("1970-01-01", "1998-01-01"))->toDateString(),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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");
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@@ -8,7 +8,7 @@ import HandHeartOutlineIcon from 'vue-material-design-icons/HandHeartOutline.vue
 | 
			
		||||
import CalendarCheckIcon from 'vue-material-design-icons/CalendarCheck.vue'
 | 
			
		||||
import MedicalBagIcon from 'vue-material-design-icons/MedicalBag.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 = [
 | 
			
		||||
  {
 | 
			
		||||
@@ -43,8 +43,8 @@ const types = [
 | 
			
		||||
    text: 'Urlop szkoleniowy',
 | 
			
		||||
    value: 'training_vacation',
 | 
			
		||||
    icon: HumanMaleBoardIcon,
 | 
			
		||||
    color: 'text-blumilk-500',
 | 
			
		||||
    border: 'border-blumilk-500',
 | 
			
		||||
    color: 'text-indigo-500',
 | 
			
		||||
    border: 'border-indigo-500',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    text: 'Urlop bezpłatny',
 | 
			
		||||
@@ -84,9 +84,9 @@ const types = [
 | 
			
		||||
  {
 | 
			
		||||
    text: 'Praca zdalna',
 | 
			
		||||
    value: 'home_office',
 | 
			
		||||
    icon: LaptopIcon,
 | 
			
		||||
    color: 'text-fuchsia-500',
 | 
			
		||||
    border: 'border-fuchsia-500',
 | 
			
		||||
    icon: HomeCityIcon,
 | 
			
		||||
    color: 'text-blumilk-500',
 | 
			
		||||
    border: 'border-blumilk-500',
 | 
			
		||||
  },
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -234,6 +234,52 @@
 | 
			
		||||
          </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="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="space-x-3">
 | 
			
		||||
          <InertiaLink
 | 
			
		||||
@@ -274,6 +320,8 @@ const form = useForm({
 | 
			
		||||
  role: props.roles[0],
 | 
			
		||||
  position: null,
 | 
			
		||||
  employmentDate: null,
 | 
			
		||||
  birthday: null,
 | 
			
		||||
  slackId: null,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function createUser() {
 | 
			
		||||
 
 | 
			
		||||
@@ -241,6 +241,52 @@
 | 
			
		||||
          </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="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="space-x-3">
 | 
			
		||||
          <InertiaLink
 | 
			
		||||
@@ -282,6 +328,8 @@ const form = useForm({
 | 
			
		||||
  position: props.user.position,
 | 
			
		||||
  employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
 | 
			
		||||
  employmentDate: props.user.employmentDate,
 | 
			
		||||
  birthday: props.user.birthday,
 | 
			
		||||
  slackId: props.user.slackId,
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function editUser() {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,11 @@
 | 
			
		||||
  "cancelled": "anulowany",
 | 
			
		||||
  "rejected": "odrzucony",
 | 
			
		||||
  "approved": "zatwierdzony",
 | 
			
		||||
  "You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.",
 | 
			
		||||
  "You have approved vacation request in this range.": "Masz zaakceptowany 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 w tym zakresie dat.",
 | 
			
		||||
  "Vacation limit has been exceeded.": "Limit urlopu został przekroczony.",
 | 
			
		||||
  "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 updated.": "Użytkownik został zaktualizowany.",
 | 
			
		||||
  "User has been deleted.": "Użytkownik został usunięty.",
 | 
			
		||||
@@ -37,11 +37,11 @@
 | 
			
		||||
  "Holiday has been deleted.": "Dzień wolny został usunięty.",
 | 
			
		||||
  "Selected year period has been changed.": "Wybrany rok został zmieniony.",
 | 
			
		||||
  "Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.",
 | 
			
		||||
  "Vacation request has been created.": "Wniosek urlopowy został utworzony.",
 | 
			
		||||
  "Vacation request has been accepted.": "Wniosek urlopowy został zaakceptowany.",
 | 
			
		||||
  "Vacation request has been approved.": "Wniosek urlopowy został zatwierdzony.",
 | 
			
		||||
  "Vacation request has been rejected.": "Wniosek urlopowy został odrzucony.",
 | 
			
		||||
  "Vacation request has been cancelled.": "Wniosek urlopowy został anulowany.",
 | 
			
		||||
  "Vacation request has been created.": "Wniosek został utworzony.",
 | 
			
		||||
  "Vacation request has been accepted.": "Wniosek został zaakceptowany.",
 | 
			
		||||
  "Vacation request has been approved.": "Wniosek został zatwierdzony.",
 | 
			
		||||
  "Vacation request has been rejected.": "Wniosek został odrzucony.",
 | 
			
		||||
  "Vacation request has been cancelled.": "Wniosek został anulowany.",
 | 
			
		||||
  "Sum:": "Suma:",
 | 
			
		||||
  "Date": "Data",
 | 
			
		||||
  "Day of week": "Dzień tygodnia",
 | 
			
		||||
@@ -56,7 +56,7 @@
 | 
			
		||||
  "All rights reserved.": "Wszelkie prawa zastrzeżone",
 | 
			
		||||
  "Show vacation request": "Pokaż wniosek",
 | 
			
		||||
  "Vacation request :title has been created" : "Wniosek :title został utworzony",
 | 
			
		||||
  "The vacation request :title has been created correctly in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title.",
 | 
			
		||||
  "The vacation request :title from user :requester has been created successfully.": "Wniosek :title użytkownika :requester został utworzony pomyślnie.",
 | 
			
		||||
  "Vacation type: :type": "Rodzaj wniosku: :type",
 | 
			
		||||
  "From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)",
 | 
			
		||||
  "Click here for details": "Kliknij, aby zobaczyć szczegóły",
 | 
			
		||||
@@ -67,9 +67,11 @@
 | 
			
		||||
  "Vacation request :title has been :status": "Wniosek :title został :status",
 | 
			
		||||
  "The vacation request :title from user :requester has been :status.": "Wniosek urlopowy :title użytkownika :requester został :status.",
 | 
			
		||||
  "Vacation request :title has been created on your behalf": "Wniosek urlopowy :title został utworzony w Twoim imieniu",
 | 
			
		||||
  "The vacation request :title has been created correctly by user :creator on your behalf in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title w Twoim imieniu przez użytkownika :creator.",
 | 
			
		||||
  "The vacation request :title has been created successfully by user :creator on your behalf.": "Wniosek urlopowy :title został pomyślnie utworzony w Twoim imieniu przez użytkownika :creator.",
 | 
			
		||||
  "Key no :number has been created.": "Klucz nr :number został utworzony.",
 | 
			
		||||
  "Key no :number has been deleted.": "Klucz nr :number został usunięty.",
 | 
			
		||||
  "Key no :number has been taken from :user.": "Klucz nr :number został zabrany użytkownikowi :user.",
 | 
			
		||||
  "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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,9 @@ use Toby\Infrastructure\Http\Controllers\Api\CalculateUserUnavailableDaysControl
 | 
			
		||||
use Toby\Infrastructure\Http\Controllers\Api\CalculateUserVacationStatsController;
 | 
			
		||||
use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController;
 | 
			
		||||
use Toby\Infrastructure\Http\Controllers\Api\GetAvailableVacationTypesController;
 | 
			
		||||
use Toby\Infrastructure\Slack\Controller as SlackController;
 | 
			
		||||
 | 
			
		||||
Route::post("slack", [SlackController::class, "getResponse"]);
 | 
			
		||||
 | 
			
		||||
Route::middleware("auth:sanctum")->group(function (): void {
 | 
			
		||||
    Route::post("vacation/calculate-days", CalculateVacationDaysController::class);
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
 | 
			
		||||
namespace Tests\Feature;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
 | 
			
		||||
use Illuminate\Support\Facades\Notification;
 | 
			
		||||
use Inertia\Testing\AssertableInertia as Assert;
 | 
			
		||||
use Tests\FeatureTestCase;
 | 
			
		||||
use Toby\Eloquent\Models\Key;
 | 
			
		||||
@@ -14,6 +15,13 @@ class KeyTest extends FeatureTestCase
 | 
			
		||||
{
 | 
			
		||||
    use DatabaseMigrations;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        parent::setUp();
 | 
			
		||||
 | 
			
		||||
        Notification::fake();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testUserCanSeeKeyList(): void
 | 
			
		||||
    {
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user