Merge branch 'main' into #43-vacation-summary-for-employee
# Conflicts: # app/Infrastructure/Http/Controllers/HolidayController.php # app/Infrastructure/Http/Controllers/VacationLimitController.php # app/Infrastructure/Http/Controllers/VacationRequestController.php # composer.lock # resources/js/Pages/Holidays/Index.vue # resources/js/Pages/VacationRequest/Create.vue
This commit is contained in:
		@@ -5,13 +5,31 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Architecture\Providers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
 | 
			
		||||
use Illuminate\Support\Facades\Gate;
 | 
			
		||||
use Toby\Domain\Enums\Role;
 | 
			
		||||
use Toby\Domain\Policies\VacationRequestPolicy;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class AuthServiceProvider extends ServiceProvider
 | 
			
		||||
{
 | 
			
		||||
    protected $policies = [];
 | 
			
		||||
    protected $policies = [
 | 
			
		||||
        VacationRequest::class => VacationRequestPolicy::class,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function boot(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->registerPolicies();
 | 
			
		||||
 | 
			
		||||
        Gate::before(function (User $user) {
 | 
			
		||||
            if ($user->role === Role::Administrator) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,9 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Carbon\CarbonPeriod;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\Vacation;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
@@ -54,7 +54,7 @@ class CalendarGenerator
 | 
			
		||||
    {
 | 
			
		||||
        return Vacation::query()
 | 
			
		||||
            ->whereBetween("date", [$period->start, $period->end])
 | 
			
		||||
            ->whereRelation("vacationRequest", "state", VacationRequestState::Approved->value)
 | 
			
		||||
            ->whereRelation("vacationRequest", fn(Builder $query) => $query->states(VacationRequestStatesRetriever::successStates()))
 | 
			
		||||
            ->get()
 | 
			
		||||
            ->groupBy(fn(Vacation $vacation) => $vacation->date->toDateString());
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Enums;
 | 
			
		||||
 | 
			
		||||
enum VacationRequestState: string
 | 
			
		||||
{
 | 
			
		||||
    case Created = "created";
 | 
			
		||||
    case Cancelled = "cancelled";
 | 
			
		||||
    case Rejected = "rejected";
 | 
			
		||||
    case Approved = "approved";
 | 
			
		||||
    case WaitingForTechnical = "waiting_for_technical";
 | 
			
		||||
    case WaitingForAdministrative = "waiting_for_administrative";
 | 
			
		||||
    case AcceptedByTechnical = "accepted_by_technical";
 | 
			
		||||
    case AcceptedByAdministrative = "accepted_by_administrative";
 | 
			
		||||
 | 
			
		||||
    public function label(): string
 | 
			
		||||
    {
 | 
			
		||||
        return __($this->value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function pendingStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            self::Created,
 | 
			
		||||
            self::WaitingForTechnical,
 | 
			
		||||
            self::WaitingForAdministrative,
 | 
			
		||||
            self::AcceptedByTechnical,
 | 
			
		||||
            self::AcceptedByAdministrative,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function successStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [self::Approved];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function failedStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            self::Rejected,
 | 
			
		||||
            self::Cancelled,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function filterByStatus(string $filter): array
 | 
			
		||||
    {
 | 
			
		||||
        return match ($filter) {
 | 
			
		||||
            "pending" => VacationRequestState::pendingStates(),
 | 
			
		||||
            "success" => VacationRequestState::successStates(),
 | 
			
		||||
            "failed" => VacationRequestState::failedStates(),
 | 
			
		||||
            default => VacationRequestState::cases(),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ namespace Toby\Domain\Events;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Events\Dispatchable;
 | 
			
		||||
use Illuminate\Queue\SerializesModels;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										51
									
								
								app/Domain/Policies/VacationRequestPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/Domain/Policies/VacationRequestPolicy.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Policies;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\Enums\Role;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestPolicy
 | 
			
		||||
{
 | 
			
		||||
    public function createOnBehalfOfEmployee(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $user->role === Role::AdministrativeApprover;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function acceptAsAdminApprover(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $user->role === Role::AdministrativeApprover;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function acceptAsTechApprover(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $user->role === Role::TechnicalApprover;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function skipFlow(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $user->role === Role::AdministrativeApprover;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function reject(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return in_array($user->role, [Role::AdministrativeApprover, Role::TechnicalApprover], true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function cancel(User $user): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $user->role === Role::AdministrativeApprover;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function show(User $user, VacationRequest $vacationRequest): bool
 | 
			
		||||
    {
 | 
			
		||||
        if ($vacationRequest->user->is($user)) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return in_array($user->role, [Role::TechnicalApprover, Role::AdministrativeApprover], true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class AcceptedByAdministrative extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "accepted_by_administrative";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/AcceptedByTechnical.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/AcceptedByTechnical.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class AcceptedByTechnical extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "accepted_by_technical";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/Approved.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/Approved.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class Approved extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "approved";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/Cancelled.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/Cancelled.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class Cancelled extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "cancelled";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/Created.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/Created.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class Created extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "created";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/Rejected.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/Rejected.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class Rejected extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "rejected";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								app/Domain/States/VacationRequest/VacationRequestState.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/Domain/States/VacationRequest/VacationRequestState.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
use Spatie\ModelStates\Exceptions\InvalidConfig;
 | 
			
		||||
use Spatie\ModelStates\State;
 | 
			
		||||
use Spatie\ModelStates\StateConfig;
 | 
			
		||||
 | 
			
		||||
abstract class VacationRequestState extends State
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws InvalidConfig
 | 
			
		||||
     */
 | 
			
		||||
    public static function config(): StateConfig
 | 
			
		||||
    {
 | 
			
		||||
        return parent::config()
 | 
			
		||||
            ->default(Created::class)
 | 
			
		||||
            ->allowTransition(Created::class, Approved::class)
 | 
			
		||||
            ->allowTransition(Created::class, WaitingForTechnical::class)
 | 
			
		||||
            ->allowTransition(Created::class, WaitingForAdministrative::class)
 | 
			
		||||
            ->allowTransition(WaitingForTechnical::class, Rejected::class)
 | 
			
		||||
            ->allowTransition(WaitingForTechnical::class, AcceptedByTechnical::class)
 | 
			
		||||
            ->allowTransition(WaitingForAdministrative::class, Rejected::class)
 | 
			
		||||
            ->allowTransition(WaitingForAdministrative::class, AcceptedByAdministrative::class)
 | 
			
		||||
            ->allowTransition(AcceptedByTechnical::class, WaitingForAdministrative::class)
 | 
			
		||||
            ->allowTransition(AcceptedByTechnical::class, Approved::class)
 | 
			
		||||
            ->allowTransition(AcceptedByAdministrative::class, Approved::class)
 | 
			
		||||
            ->allowTransition([
 | 
			
		||||
                Created::class,
 | 
			
		||||
                WaitingForTechnical::class,
 | 
			
		||||
                WaitingForAdministrative::class,
 | 
			
		||||
                AcceptedByTechnical::class,
 | 
			
		||||
                AcceptedByAdministrative::class,
 | 
			
		||||
                Approved::class,
 | 
			
		||||
            ], Cancelled::class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class WaitingForAdministrative extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "waiting_for_administrative";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								app/Domain/States/VacationRequest/WaitingForTechnical.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/States/VacationRequest/WaitingForTechnical.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\States\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class WaitingForTechnical extends VacationRequestState
 | 
			
		||||
{
 | 
			
		||||
    public static string $name = "waiting_for_technical";
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,6 @@ namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
@@ -26,7 +25,7 @@ class UserVacationStatsRetriever
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->states(VacationRequestState::successStates()),
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->count();
 | 
			
		||||
    }
 | 
			
		||||
@@ -40,7 +39,7 @@ class UserVacationStatsRetriever
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->states(VacationRequestState::pendingStates()),
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::pendingStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->count();
 | 
			
		||||
    }
 | 
			
		||||
@@ -54,7 +53,7 @@ class UserVacationStatsRetriever
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                    ->whereIn("type", $this->getNotLimitableVacationTypes())
 | 
			
		||||
                    ->states(VacationRequestState::successStates()),
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->count();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,24 @@ namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Auth\Factory as Auth;
 | 
			
		||||
use Illuminate\Contracts\Events\Dispatcher;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestApproved;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestCancelled;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestCreated;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestRejected;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestStateChanged;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestWaitsForAdminApproval;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestWaitsForTechApproval;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByAdministrative;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Approved;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Cancelled;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Rejected;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\WaitingForAdministrative;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestStateManager
 | 
			
		||||
@@ -24,64 +33,77 @@ class VacationRequestStateManager
 | 
			
		||||
        protected Dispatcher $dispatcher,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function markAsCreated(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function markAsCreated(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::Created);
 | 
			
		||||
        $this->fireStateChangedEvent($vacationRequest, null, $vacationRequest->state, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function approve(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function approve(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::Approved);
 | 
			
		||||
        $this->changeState($vacationRequest, Approved::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function reject(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function reject(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::Rejected);
 | 
			
		||||
 | 
			
		||||
        $this->changeState($vacationRequest, Rejected::class, $user);
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestRejected($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function cancel(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function cancel(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::Cancelled);
 | 
			
		||||
        $this->changeState($vacationRequest, Cancelled::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestCancelled($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function acceptAsTechnical(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function acceptAsTechnical(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::AcceptedByTechnical);
 | 
			
		||||
        $this->changeState($vacationRequest, AcceptedByTechnical::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function acceptAsAdministrative(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function acceptAsAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::AcceptedByAdministrative);
 | 
			
		||||
        $this->changeState($vacationRequest, AcceptedByAdministrative::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function waitForTechnical(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::WaitingForTechnical);
 | 
			
		||||
        $this->changeState($vacationRequest, WaitingForTechnical::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function waitForAdministrative(VacationRequest $vacationRequest): void
 | 
			
		||||
    public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, VacationRequestState::WaitingForAdministrative);
 | 
			
		||||
        $this->changeState($vacationRequest, WaitingForAdministrative::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void
 | 
			
		||||
    protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $vacationRequest->changeStateTo($state);
 | 
			
		||||
        $previousState = $vacationRequest->state;
 | 
			
		||||
        $vacationRequest->state->transitionTo($state);
 | 
			
		||||
        $vacationRequest->save();
 | 
			
		||||
 | 
			
		||||
        $this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state, $user);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function fireStateChangedEvent(
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        ?VacationRequestState $from,
 | 
			
		||||
        VacationRequestState $to,
 | 
			
		||||
        ?User $user = null,
 | 
			
		||||
    ): void {
 | 
			
		||||
        $event = new VacationRequestStateChanged($vacationRequest, $from, $to, $user);
 | 
			
		||||
        $this->dispatcher->dispatch($event);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										60
									
								
								app/Domain/VacationRequestStatesRetriever.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								app/Domain/VacationRequestStatesRetriever.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByAdministrative;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Approved;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Cancelled;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Created;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Rejected;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\WaitingForAdministrative;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
 | 
			
		||||
 | 
			
		||||
class VacationRequestStatesRetriever
 | 
			
		||||
{
 | 
			
		||||
    public static function pendingStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            Created::class,
 | 
			
		||||
            WaitingForTechnical::class,
 | 
			
		||||
            WaitingForAdministrative::class,
 | 
			
		||||
            AcceptedByTechnical::class,
 | 
			
		||||
            AcceptedByAdministrative::class,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function successStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [Approved::class];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function failedStates(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            Rejected::class,
 | 
			
		||||
            Cancelled::class,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function all(): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
            ...self::pendingStates(),
 | 
			
		||||
            ...self::successStates(),
 | 
			
		||||
            ...self::failedStates(),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function filterByStatus(string $filter): array
 | 
			
		||||
    {
 | 
			
		||||
        return match ($filter) {
 | 
			
		||||
            "pending" => self::pendingStates(),
 | 
			
		||||
            "success" => self::successStates(),
 | 
			
		||||
            "failed" => self::failedStates(),
 | 
			
		||||
            default => self::all(),
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -6,9 +6,9 @@ namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\VacationDaysCalculator;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Domain\VacationTypeConfigRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
@@ -52,7 +52,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->noStates(VacationRequestState::failedStates()),
 | 
			
		||||
                    ->noStates(VacationRequestStatesRetriever::failedStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->count();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class NoApprovedVacationRequestsInRange implements VacationRequestRule
 | 
			
		||||
@@ -15,7 +15,7 @@ class NoApprovedVacationRequestsInRange implements VacationRequestRule
 | 
			
		||||
            ->user
 | 
			
		||||
            ->vacationRequests()
 | 
			
		||||
            ->overlapsWith($vacationRequest)
 | 
			
		||||
            ->states(VacationRequestState::successStates())
 | 
			
		||||
            ->states(VacationRequestStatesRetriever::successStates())
 | 
			
		||||
            ->exists();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class NoPendingVacationRequestInRange implements VacationRequestRule
 | 
			
		||||
@@ -15,7 +15,7 @@ class NoPendingVacationRequestInRange implements VacationRequestRule
 | 
			
		||||
            ->user
 | 
			
		||||
            ->vacationRequests()
 | 
			
		||||
            ->overlapsWith($vacationRequest)
 | 
			
		||||
            ->states(VacationRequestState::pendingStates())
 | 
			
		||||
            ->states(VacationRequestStatesRetriever::pendingStates())
 | 
			
		||||
            ->exists();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasMany;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Spatie\ModelStates\HasStates;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
@@ -34,6 +35,7 @@ use Toby\Domain\Enums\VacationType;
 | 
			
		||||
class VacationRequest extends Model
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
    use HasStates;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
@@ -69,26 +71,14 @@ class VacationRequest extends Model
 | 
			
		||||
        return $this->hasMany(Vacation::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function changeStateTo(VacationRequestState $state): void
 | 
			
		||||
    public function scopeStates(Builder $query, VacationRequestState|array $states): Builder
 | 
			
		||||
    {
 | 
			
		||||
        $this->state = $state;
 | 
			
		||||
 | 
			
		||||
        $this->save();
 | 
			
		||||
        return $query->whereState("state", $states);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasFlowSkipped(): bool
 | 
			
		||||
    public function scopeNoStates(Builder $query, VacationRequestState|array $states): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $this->flow_skipped;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scopeStates(Builder $query, array $states): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $query->whereIn("state", $states);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scopeNoStates(Builder $query, array $states): Builder
 | 
			
		||||
    {
 | 
			
		||||
        return $query->whereNotIn("state", $states);
 | 
			
		||||
        return $query->whereNotState("state", $states);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder
 | 
			
		||||
@@ -97,6 +87,11 @@ class VacationRequest extends Model
 | 
			
		||||
            ->where("to", ">=", $vacationRequest->from);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasFlowSkipped(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->flow_skipped;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected static function newFactory(): VacationRequestFactory
 | 
			
		||||
    {
 | 
			
		||||
        return VacationRequestFactory::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ use Database\Factories\VacationRequestActivityFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,6 @@ namespace Toby\Eloquent\Observers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Auth\Factory as Auth;
 | 
			
		||||
use Illuminate\Contracts\Events\Dispatcher;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\Events\VacationRequestStateChanged;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestObserver
 | 
			
		||||
@@ -28,31 +25,4 @@ class VacationRequestObserver
 | 
			
		||||
 | 
			
		||||
        $vacationRequest->name = "{$vacationRequestNumber}/${year}";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function saved(VacationRequest $vacationRequest): void
 | 
			
		||||
    {
 | 
			
		||||
        if ($vacationRequest->isDirty("state")) {
 | 
			
		||||
            $previousState = $vacationRequest->getOriginal("state");
 | 
			
		||||
 | 
			
		||||
            $this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function fireStateChangedEvent(
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        ?VacationRequestState $from,
 | 
			
		||||
        VacationRequestState $to,
 | 
			
		||||
    ): void {
 | 
			
		||||
        $event = new VacationRequestStateChanged($vacationRequest, $from, $to, $this->getAuthUser());
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch($event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function getAuthUser(): ?User
 | 
			
		||||
    {
 | 
			
		||||
        /** @var User $user */
 | 
			
		||||
        $user = $this->auth->guard()->user();
 | 
			
		||||
 | 
			
		||||
        return $user;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,8 @@ use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\UserVacationStatsRetriever;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\Holiday;
 | 
			
		||||
use Toby\Eloquent\Models\Vacation;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
@@ -31,7 +31,7 @@ class DashboardController extends Controller
 | 
			
		||||
            ->whereDate("date", $now)
 | 
			
		||||
            ->whereRelation(
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query->states(VacationRequestState::successStates()),
 | 
			
		||||
                fn(Builder $query) => $query->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->get();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,9 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
use Illuminate\Http\RedirectResponse;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\Holiday;
 | 
			
		||||
@@ -14,7 +16,7 @@ use Toby\Infrastructure\Http\Resources\HolidayResource;
 | 
			
		||||
 | 
			
		||||
class HolidayController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function index(YearPeriodRetriever $yearPeriodRetriever): Response
 | 
			
		||||
    public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
 | 
			
		||||
    {
 | 
			
		||||
        $yearPeriod = $yearPeriodRetriever->selected();
 | 
			
		||||
 | 
			
		||||
@@ -25,16 +27,29 @@ class HolidayController extends Controller
 | 
			
		||||
 | 
			
		||||
        return inertia("Holidays/Index", [
 | 
			
		||||
            "holidays" => HolidayResource::collection($holidays),
 | 
			
		||||
            "can" => [
 | 
			
		||||
                "manageHolidays" => $request->user()->can("manageHolidays"),
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function create(): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageHolidays");
 | 
			
		||||
 | 
			
		||||
        return inertia("Holidays/Create");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function store(HolidayRequest $request): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageHolidays");
 | 
			
		||||
 | 
			
		||||
        Holiday::query()->create($request->data());
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
@@ -42,15 +57,25 @@ class HolidayController extends Controller
 | 
			
		||||
            ->with("success", __("Holiday has been created."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function edit(Holiday $holiday): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageHolidays");
 | 
			
		||||
 | 
			
		||||
        return inertia("Holidays/Edit", [
 | 
			
		||||
            "holiday" => new HolidayFormDataResource($holiday),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function update(HolidayRequest $request, Holiday $holiday): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageHolidays");
 | 
			
		||||
 | 
			
		||||
        $holiday->update($request->data());
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
@@ -58,8 +83,13 @@ class HolidayController extends Controller
 | 
			
		||||
            ->with("success", __("Holiday has been updated."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function destroy(Holiday $holiday): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageHolidays");
 | 
			
		||||
 | 
			
		||||
        $holiday->delete();
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,8 @@ class TimesheetController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function __invoke(Month $month, YearPeriodRetriever $yearPeriodRetriever): BinaryFileResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("generateTimesheet");
 | 
			
		||||
 | 
			
		||||
        $yearPeriod = $yearPeriodRetriever->selected();
 | 
			
		||||
        $carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber());
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
use Illuminate\Http\RedirectResponse;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
@@ -16,8 +17,13 @@ use Toby\Infrastructure\Http\Resources\UserResource;
 | 
			
		||||
 | 
			
		||||
class UserController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function index(Request $request): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        $users = User::query()
 | 
			
		||||
            ->withTrashed()
 | 
			
		||||
            ->search($request->query("search"))
 | 
			
		||||
@@ -32,16 +38,26 @@ class UserController extends Controller
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function create(): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        return inertia("Users/Create", [
 | 
			
		||||
            "employmentForms" => EmploymentForm::casesToSelect(),
 | 
			
		||||
            "roles" => Role::casesToSelect(),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function store(UserRequest $request): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        User::query()->create($request->data());
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
@@ -49,8 +65,13 @@ class UserController extends Controller
 | 
			
		||||
            ->with("success", __("User has been created."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function edit(User $user): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        return inertia("Users/Edit", [
 | 
			
		||||
            "user" => new UserFormDataResource($user),
 | 
			
		||||
            "employmentForms" => EmploymentForm::casesToSelect(),
 | 
			
		||||
@@ -58,8 +79,13 @@ class UserController extends Controller
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function update(UserRequest $request, User $user): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        $user->update($request->data());
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
@@ -67,8 +93,13 @@ class UserController extends Controller
 | 
			
		||||
            ->with("success", __("User has been updated."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function destroy(User $user): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        $user->delete();
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
@@ -76,8 +107,13 @@ class UserController extends Controller
 | 
			
		||||
            ->with("success", __("User has been deleted."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function restore(User $user): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageUsers");
 | 
			
		||||
 | 
			
		||||
        $user->restore();
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Toby\Domain\CalendarGenerator;
 | 
			
		||||
@@ -15,6 +16,7 @@ use Toby\Infrastructure\Http\Resources\UserResource;
 | 
			
		||||
class VacationCalendarController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function index(
 | 
			
		||||
        Request $request,
 | 
			
		||||
        YearPeriodRetriever $yearPeriodRetriever,
 | 
			
		||||
        CalendarGenerator $calendarGenerator,
 | 
			
		||||
        ?string $month = null,
 | 
			
		||||
@@ -35,6 +37,9 @@ class VacationCalendarController extends Controller
 | 
			
		||||
            "calendar" => $calendar,
 | 
			
		||||
            "currentMonth" => $month->value,
 | 
			
		||||
            "users" => UserResource::collection($users),
 | 
			
		||||
            "can" => [
 | 
			
		||||
                "generateTimesheet" => $request->user()->can("generateTimesheet"),
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,8 @@ class VacationLimitController extends Controller
 | 
			
		||||
{
 | 
			
		||||
    public function edit(YearPeriodRetriever $yearPeriodRetriever, UserVacationStatsRetriever $statsRetriever): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageVacationLimits");
 | 
			
		||||
 | 
			
		||||
        $yearPeriod = $yearPeriodRetriever->selected();
 | 
			
		||||
        $previousYearPeriod = YearPeriod::findByYear($yearPeriod->year - 1);
 | 
			
		||||
 | 
			
		||||
@@ -44,6 +46,8 @@ class VacationLimitController extends Controller
 | 
			
		||||
 | 
			
		||||
    public function update(VacationLimitRequest $request): RedirectResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("manageVacationLimits");
 | 
			
		||||
 | 
			
		||||
        $data = $request->data();
 | 
			
		||||
 | 
			
		||||
        foreach ($request->vacationLimits() as $limit) {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,21 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Infrastructure\Http\Controllers;
 | 
			
		||||
 | 
			
		||||
use Barryvdh\DomPDF\Facade\Pdf;
 | 
			
		||||
use Illuminate\Auth\Access\AuthorizationException;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Http\RedirectResponse;
 | 
			
		||||
use Illuminate\Http\Request;
 | 
			
		||||
use Illuminate\Http\Response as LaravelResponse;
 | 
			
		||||
use Illuminate\Validation\ValidationException;
 | 
			
		||||
use Inertia\Response;
 | 
			
		||||
use Toby\Domain\Enums\VacationRequestState;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByAdministrative;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Cancelled;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Rejected;
 | 
			
		||||
use Toby\Domain\VacationDaysCalculator;
 | 
			
		||||
use Toby\Domain\VacationRequestStateManager;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Domain\Validation\VacationRequestValidator;
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
@@ -34,7 +40,7 @@ class VacationRequestController extends Controller
 | 
			
		||||
            ->with("vacations")
 | 
			
		||||
            ->where("year_period_id", $yearPeriodRetriever->selected()->id)
 | 
			
		||||
            ->latest()
 | 
			
		||||
            ->states(VacationRequestState::filterByStatus($status))
 | 
			
		||||
            ->states(VacationRequestStatesRetriever::filterByStatus($status))
 | 
			
		||||
            ->paginate();
 | 
			
		||||
 | 
			
		||||
        return inertia("VacationRequest/Index", [
 | 
			
		||||
@@ -45,16 +51,37 @@ class VacationRequestController extends Controller
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function show(VacationRequest $vacationRequest): Response
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function show(Request $request, VacationRequest $vacationRequest): Response
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("show", $vacationRequest);
 | 
			
		||||
        $user = $request->user();
 | 
			
		||||
 | 
			
		||||
        return inertia("VacationRequest/Show", [
 | 
			
		||||
            "request" => new VacationRequestResource($vacationRequest),
 | 
			
		||||
            "activities" => VacationRequestActivityResource::collection($vacationRequest->activities),
 | 
			
		||||
            "can" => [
 | 
			
		||||
                "acceptAsTechnical" => $vacationRequest->state->canTransitionTo(AcceptedByTechnical::class)
 | 
			
		||||
                    && $user->can("acceptAsTechApprover", $vacationRequest),
 | 
			
		||||
                "acceptAsAdministrative" => $vacationRequest->state->canTransitionTo(AcceptedByAdministrative::class)
 | 
			
		||||
                    && $user->can("acceptAsAdminApprover", $vacationRequest),
 | 
			
		||||
                "reject" => $vacationRequest->state->canTransitionTo(Rejected::class)
 | 
			
		||||
                    && $user->can("reject", $vacationRequest),
 | 
			
		||||
                "cancel" => $vacationRequest->state->canTransitionTo(Cancelled::class)
 | 
			
		||||
                    && $user->can("cancel", $vacationRequest),
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function download(VacationRequest $vacationRequest): LaravelResponse
 | 
			
		||||
    {
 | 
			
		||||
        $this->authorize("show", $vacationRequest);
 | 
			
		||||
 | 
			
		||||
        $pdf = PDF::loadView("pdf.vacation-request", [
 | 
			
		||||
            "vacationRequest" => $vacationRequest,
 | 
			
		||||
        ]);
 | 
			
		||||
@@ -62,7 +89,7 @@ class VacationRequestController extends Controller
 | 
			
		||||
        return $pdf->stream();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function create(YearPeriodRetriever $yearPeriodRetriever): Response
 | 
			
		||||
    public function create(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
 | 
			
		||||
    {
 | 
			
		||||
        $yearPeriod = $yearPeriodRetriever->selected();
 | 
			
		||||
 | 
			
		||||
@@ -78,15 +105,31 @@ class VacationRequestController extends Controller
 | 
			
		||||
        return inertia("VacationRequest/Create", [
 | 
			
		||||
            "vacationTypes" => VacationType::casesToSelect(),
 | 
			
		||||
            "users" => UserResource::collection($users),
 | 
			
		||||
            "can" => [
 | 
			
		||||
                "createOnBehalfOfEmployee" => $request->user()->can("createOnBehalfOfEmployee", VacationRequest::class),
 | 
			
		||||
                "skipFlow" => $request->user()->can("skipFlow", VacationRequest::class),
 | 
			
		||||
            ],
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     * @throws ValidationException
 | 
			
		||||
     */
 | 
			
		||||
    public function store(
 | 
			
		||||
        VacationRequestRequest $request,
 | 
			
		||||
        VacationRequestValidator $vacationRequestValidator,
 | 
			
		||||
        VacationRequestStateManager $stateManager,
 | 
			
		||||
        VacationDaysCalculator $vacationDaysCalculator,
 | 
			
		||||
    ): RedirectResponse {
 | 
			
		||||
        if ($request->createsOnBehalfOfEmployee()) {
 | 
			
		||||
            $this->authorize("createOnBehalfOfEmployee", VacationRequest::class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if ($request->wantsSkipFlow()) {
 | 
			
		||||
            $this->authorize("skipFlow", VacationRequest::class);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /** @var VacationRequest $vacationRequest */
 | 
			
		||||
        $vacationRequest = $request->user()->createdVacationRequests()->make($request->data());
 | 
			
		||||
        $vacationRequestValidator->validate($vacationRequest);
 | 
			
		||||
@@ -107,48 +150,72 @@ class VacationRequestController extends Controller
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $stateManager->markAsCreated($vacationRequest);
 | 
			
		||||
        $stateManager->markAsCreated($vacationRequest, $request->user());
 | 
			
		||||
 | 
			
		||||
        return redirect()
 | 
			
		||||
            ->route("vacation.requests.show", $vacationRequest)
 | 
			
		||||
            ->with("success", __("Vacation request has been created."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function reject(
 | 
			
		||||
        Request $request,
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        VacationRequestStateManager $stateManager,
 | 
			
		||||
    ): RedirectResponse {
 | 
			
		||||
        $stateManager->reject($vacationRequest);
 | 
			
		||||
        $this->authorize("reject", $vacationRequest);
 | 
			
		||||
 | 
			
		||||
        $stateManager->reject($vacationRequest, $request->user());
 | 
			
		||||
 | 
			
		||||
        return redirect()->back()
 | 
			
		||||
            ->with("success", __("Vacation request has been rejected."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function cancel(
 | 
			
		||||
        Request $request,
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        VacationRequestStateManager $stateManager,
 | 
			
		||||
    ): RedirectResponse {
 | 
			
		||||
        $stateManager->cancel($vacationRequest);
 | 
			
		||||
        $this->authorize("cancel", $vacationRequest);
 | 
			
		||||
 | 
			
		||||
        $stateManager->cancel($vacationRequest, $request->user());
 | 
			
		||||
 | 
			
		||||
        return redirect()->back()
 | 
			
		||||
            ->with("success", __("Vacation request has been cancelled."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function acceptAsTechnical(
 | 
			
		||||
        Request $request,
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        VacationRequestStateManager $stateManager,
 | 
			
		||||
    ): RedirectResponse {
 | 
			
		||||
        $stateManager->acceptAsTechnical($vacationRequest);
 | 
			
		||||
        $this->authorize("acceptAsTechApprover", $vacationRequest);
 | 
			
		||||
 | 
			
		||||
        $stateManager->acceptAsTechnical($vacationRequest, $request->user());
 | 
			
		||||
 | 
			
		||||
        return redirect()->back()
 | 
			
		||||
            ->with("success", __("Vacation request has been accepted."));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @throws AuthorizationException
 | 
			
		||||
     */
 | 
			
		||||
    public function acceptAsAdministrative(
 | 
			
		||||
        Request $request,
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        VacationRequestStateManager $stateManager,
 | 
			
		||||
    ): RedirectResponse {
 | 
			
		||||
        $stateManager->acceptAsAdministrative($vacationRequest);
 | 
			
		||||
        $this->authorize("acceptAsAdminApprover", $vacationRequest);
 | 
			
		||||
 | 
			
		||||
        $stateManager->acceptAsAdministrative($vacationRequest, $request->user());
 | 
			
		||||
 | 
			
		||||
        return redirect()->back()
 | 
			
		||||
            ->with("success", __("Vacation request has been accepted."));
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,10 @@ class HandleInertiaRequests extends Middleware
 | 
			
		||||
        return array_merge(parent::share($request), [
 | 
			
		||||
            "auth" => fn() => [
 | 
			
		||||
                "user" => $user ? new UserResource($user) : null,
 | 
			
		||||
                "can" => [
 | 
			
		||||
                    "manageVacationLimits" => $user ? $user->can("manageVacationLimits") : false,
 | 
			
		||||
                    "manageUsers" => $user ? $user->can("manageUsers") : false,
 | 
			
		||||
                ],
 | 
			
		||||
            ],
 | 
			
		||||
            "flash" => fn() => [
 | 
			
		||||
                "success" => $request->session()->get("success"),
 | 
			
		||||
 
 | 
			
		||||
@@ -27,16 +27,29 @@ class VacationRequestRequest extends FormRequest
 | 
			
		||||
 | 
			
		||||
    public function data(): array
 | 
			
		||||
    {
 | 
			
		||||
        $from = $this->get("from");
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            "user_id" => $this->get("user"),
 | 
			
		||||
            "type" => $this->get("type"),
 | 
			
		||||
            "from" => $from,
 | 
			
		||||
            "from" => $this->get("from"),
 | 
			
		||||
            "to" => $this->get("to"),
 | 
			
		||||
            "year_period_id" => YearPeriod::findByYear(Carbon::create($from)->year)->id,
 | 
			
		||||
            "year_period_id" => $this->yearPeriod()->id,
 | 
			
		||||
            "comment" => $this->get("comment"),
 | 
			
		||||
            "flow_skipped" => $this->boolean("flowSkipped"),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function yearPeriod(): YearPeriod
 | 
			
		||||
    {
 | 
			
		||||
        return YearPeriod::findByYear(Carbon::create($this->get("from"))->year);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createsOnBehalfOfEmployee(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->user()->id !== $this->get("user");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function wantsSkipFlow(): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->boolean("flowSkipped");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ class VacationRequestActivityResource extends JsonResource
 | 
			
		||||
            "time" => $this->created_at->format("H:i"),
 | 
			
		||||
            "user" => $this->user ? $this->user->fullName : __("System"),
 | 
			
		||||
            "state" => $this->to,
 | 
			
		||||
            "text" => $this->to->label(),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user