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:
		@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user