Merge branch 'main' into #22-vacation-calendar
# Conflicts: # resources/js/Pages/Dashboard.vue # resources/js/Shared/Layout/AppLayout.vue # resources/js/Shared/MainMenu.vue # routes/web.php
This commit is contained in:
@@ -5,8 +5,24 @@ declare(strict_types=1);
|
||||
namespace Toby\Architecture\Providers;
|
||||
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
|
||||
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||
use Toby\Domain\Events\VacationRequestApproved;
|
||||
use Toby\Domain\Events\VacationRequestCreated;
|
||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||
use Toby\Domain\Listeners\CreateVacationRequestActivity;
|
||||
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleCreatedVacationRequest;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
protected $listen = [];
|
||||
protected $listen = [
|
||||
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
|
||||
VacationRequestCreated::class => [HandleCreatedVacationRequest::class],
|
||||
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
|
||||
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
|
||||
VacationRequestApproved::class => [HandleApprovedVacationRequest::class],
|
||||
];
|
||||
}
|
||||
|
@@ -6,8 +6,10 @@ namespace Toby\Architecture\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
use Toby\Eloquent\Models\YearPeriod;
|
||||
use Toby\Eloquent\Observers\UserObserver;
|
||||
use Toby\Eloquent\Observers\VacationRequestObserver;
|
||||
use Toby\Eloquent\Observers\YearPeriodObserver;
|
||||
|
||||
class ObserverServiceProvider extends ServiceProvider
|
||||
@@ -16,5 +18,6 @@ class ObserverServiceProvider extends ServiceProvider
|
||||
{
|
||||
User::observe(UserObserver::class);
|
||||
YearPeriod::observe(YearPeriodObserver::class);
|
||||
VacationRequest::observe(VacationRequestObserver::class);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain;
|
||||
namespace Toby\Domain\Enums;
|
||||
|
||||
enum EmploymentForm: string
|
||||
{
|
30
app/Domain/Enums/Role.php
Normal file
30
app/Domain/Enums/Role.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Enums;
|
||||
|
||||
enum Role: string
|
||||
{
|
||||
case EMPLOYEE = "employee";
|
||||
case ADMINISTRATOR = "administrator";
|
||||
case TECHNICAL_APPROVER = "technical_approver";
|
||||
case ADMINISTRATIVE_APPROVER = "administrative_approver";
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return __($this->value);
|
||||
}
|
||||
|
||||
public static function casesToSelect(): array
|
||||
{
|
||||
$cases = collect(Role::cases());
|
||||
|
||||
return $cases->map(
|
||||
fn(Role $enum) => [
|
||||
"label" => $enum->label(),
|
||||
"value" => $enum->value,
|
||||
],
|
||||
)->toArray();
|
||||
}
|
||||
}
|
56
app/Domain/Enums/VacationRequestState.php
Normal file
56
app/Domain/Enums/VacationRequestState.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Enums;
|
||||
|
||||
enum VacationRequestState: string
|
||||
{
|
||||
case CREATED = "created";
|
||||
case CANCELED = "canceled";
|
||||
case REJECTED = "rejected";
|
||||
case APPROVED = "approved";
|
||||
case WAITING_FOR_TECHNICAL = "waiting_for_technical";
|
||||
case WAITING_FOR_ADMINISTRATIVE = "waiting_for_administrative";
|
||||
case ACCEPTED_BY_TECHNICAL = "accepted_by_technical";
|
||||
case ACCEPTED_BY_ADMINSTRATIVE = "accepted_by_administrative";
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return __($this->value);
|
||||
}
|
||||
|
||||
public static function pendingStates(): array
|
||||
{
|
||||
return [
|
||||
self::CREATED,
|
||||
self::WAITING_FOR_TECHNICAL,
|
||||
self::WAITING_FOR_ADMINISTRATIVE,
|
||||
self::ACCEPTED_BY_TECHNICAL,
|
||||
self::ACCEPTED_BY_ADMINSTRATIVE,
|
||||
];
|
||||
}
|
||||
|
||||
public static function successStates(): array
|
||||
{
|
||||
return [self::APPROVED];
|
||||
}
|
||||
|
||||
public static function failedStates(): array
|
||||
{
|
||||
return [
|
||||
self::REJECTED,
|
||||
self::CANCELED,
|
||||
];
|
||||
}
|
||||
|
||||
public static function filterByStatus(string $filter): array
|
||||
{
|
||||
return match ($filter) {
|
||||
"pending" => VacationRequestState::pendingStates(),
|
||||
"success" => VacationRequestState::successStates(),
|
||||
"failed" => VacationRequestState::failedStates(),
|
||||
default => VacationRequestState::cases(),
|
||||
};
|
||||
}
|
||||
}
|
35
app/Domain/Enums/VacationType.php
Normal file
35
app/Domain/Enums/VacationType.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Enums;
|
||||
|
||||
enum VacationType: string
|
||||
{
|
||||
case VACATION = "vacation";
|
||||
case VACATION_ON_REQUEST = "vacation_on_request";
|
||||
case SPECIAL_VACATION = "special_vacation";
|
||||
case CHILDCARE_VACATION = "childcare_vacation";
|
||||
case TRAINING_VACATION = "training_vacation";
|
||||
case UNPAID_VACATION = "unpaid_vacation";
|
||||
case VOLUNTEERING_VACATION = "volunteering_vacation";
|
||||
case TIME_IN_LIEU = "time_in_lieu";
|
||||
case SICK_VACATION = "sick_vacation";
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return __($this->value);
|
||||
}
|
||||
|
||||
public static function casesToSelect(): array
|
||||
{
|
||||
$cases = collect(VacationType::cases());
|
||||
|
||||
return $cases->map(
|
||||
fn(VacationType $enum) => [
|
||||
"label" => $enum->label(),
|
||||
"value" => $enum->value,
|
||||
],
|
||||
)->toArray();
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestAcceptedByAdministrative
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public VacationRequest $vacationRequest,
|
||||
) {
|
||||
}
|
||||
}
|
20
app/Domain/Events/VacationRequestAcceptedByTechnical.php
Normal file
20
app/Domain/Events/VacationRequestAcceptedByTechnical.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestAcceptedByTechnical
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public VacationRequest $vacationRequest,
|
||||
) {
|
||||
}
|
||||
}
|
20
app/Domain/Events/VacationRequestApproved.php
Normal file
20
app/Domain/Events/VacationRequestApproved.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestApproved
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public VacationRequest $vacationRequest,
|
||||
) {
|
||||
}
|
||||
}
|
20
app/Domain/Events/VacationRequestCreated.php
Normal file
20
app/Domain/Events/VacationRequestCreated.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestCreated
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public VacationRequest $vacationRequest,
|
||||
) {
|
||||
}
|
||||
}
|
25
app/Domain/Events/VacationRequestStateChanged.php
Normal file
25
app/Domain/Events/VacationRequestStateChanged.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Events;
|
||||
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Toby\Domain\Enums\VacationRequestState;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestStateChanged
|
||||
{
|
||||
use Dispatchable;
|
||||
use SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public VacationRequest $vacationRequest,
|
||||
public ?VacationRequestState $from,
|
||||
public VacationRequestState $to,
|
||||
public ?User $user = null,
|
||||
) {
|
||||
}
|
||||
}
|
19
app/Domain/Listeners/CreateVacationRequestActivity.php
Normal file
19
app/Domain/Listeners/CreateVacationRequestActivity.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Listeners;
|
||||
|
||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||
|
||||
class CreateVacationRequestActivity
|
||||
{
|
||||
public function handle(VacationRequestStateChanged $event): void
|
||||
{
|
||||
$event->vacationRequest->activities()->create([
|
||||
"from" => $event->from,
|
||||
"to" => $event->to,
|
||||
"user_id" => $event->user?->id,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Listeners;
|
||||
|
||||
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
|
||||
use Toby\Domain\VacationRequestStateManager;
|
||||
|
||||
class HandleAcceptedByAdministrativeVacationRequest
|
||||
{
|
||||
public function __construct(
|
||||
protected VacationRequestStateManager $stateManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(VacationRequestAcceptedByAdministrative $event): void
|
||||
{
|
||||
$this->stateManager->approve($event->vacationRequest);
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Listeners;
|
||||
|
||||
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||
use Toby\Domain\VacationRequestStateManager;
|
||||
use Toby\Domain\VacationTypeConfigRetriever;
|
||||
|
||||
class HandleAcceptedByTechnicalVacationRequest
|
||||
{
|
||||
public function __construct(
|
||||
protected VacationTypeConfigRetriever $configRetriever,
|
||||
protected VacationRequestStateManager $stateManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(VacationRequestAcceptedByTechnical $event): void
|
||||
{
|
||||
$vacationRequest = $event->vacationRequest;
|
||||
|
||||
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
|
||||
$this->stateManager->waitForAdministrative($vacationRequest);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->stateManager->approve($vacationRequest);
|
||||
}
|
||||
}
|
24
app/Domain/Listeners/HandleApprovedVacationRequest.php
Normal file
24
app/Domain/Listeners/HandleApprovedVacationRequest.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Listeners;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Toby\Domain\Events\VacationRequestApproved;
|
||||
use Toby\Domain\VacationTypeConfigRetriever;
|
||||
|
||||
class HandleApprovedVacationRequest
|
||||
{
|
||||
public function __construct(
|
||||
protected VacationTypeConfigRetriever $configRetriever,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(VacationRequestApproved $event): void
|
||||
{
|
||||
$vacationRequest = $event->vacationRequest;
|
||||
|
||||
Log::info("approved! {$vacationRequest->id}");
|
||||
}
|
||||
}
|
37
app/Domain/Listeners/HandleCreatedVacationRequest.php
Normal file
37
app/Domain/Listeners/HandleCreatedVacationRequest.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Listeners;
|
||||
|
||||
use Toby\Domain\Events\VacationRequestCreated;
|
||||
use Toby\Domain\VacationRequestStateManager;
|
||||
use Toby\Domain\VacationTypeConfigRetriever;
|
||||
|
||||
class HandleCreatedVacationRequest
|
||||
{
|
||||
public function __construct(
|
||||
protected VacationTypeConfigRetriever $configRetriever,
|
||||
protected VacationRequestStateManager $stateManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(VacationRequestCreated $event): void
|
||||
{
|
||||
$vacationRequest = $event->vacationRequest;
|
||||
|
||||
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
|
||||
$this->stateManager->waitForTechnical($vacationRequest);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
|
||||
$this->stateManager->waitForAdministrative($vacationRequest);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->stateManager->approve($vacationRequest);
|
||||
}
|
||||
}
|
76
app/Domain/VacationRequestStateManager.php
Normal file
76
app/Domain/VacationRequestStateManager.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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\VacationRequestCreated;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestStateManager
|
||||
{
|
||||
public function __construct(
|
||||
protected Auth $auth,
|
||||
protected Dispatcher $dispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function markAsCreated(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::CREATED);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
|
||||
}
|
||||
|
||||
public function approve(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::APPROVED);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
|
||||
}
|
||||
|
||||
public function reject(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::REJECTED);
|
||||
}
|
||||
|
||||
public function cancel(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::CANCELED);
|
||||
}
|
||||
|
||||
public function acceptAsTechnical(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_TECHNICAL);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
|
||||
}
|
||||
|
||||
public function acceptAsAdministrative(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_ADMINSTRATIVE);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
|
||||
}
|
||||
|
||||
public function waitForTechnical(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_TECHNICAL);
|
||||
}
|
||||
|
||||
public function waitForAdministrative(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_ADMINISTRATIVE);
|
||||
}
|
||||
|
||||
protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void
|
||||
{
|
||||
$vacationRequest->changeStateTo($state);
|
||||
}
|
||||
}
|
46
app/Domain/VacationTypeConfigRetriever.php
Normal file
46
app/Domain/VacationTypeConfigRetriever.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain;
|
||||
|
||||
use Illuminate\Contracts\Config\Repository;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
|
||||
class VacationTypeConfigRetriever
|
||||
{
|
||||
public const KEY_TECHNICAL_APPROVAL = "technical_approval";
|
||||
public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval";
|
||||
public const KEY_BILLABLE = "billable";
|
||||
public const KEY_HAS_LIMIT = "has_limit";
|
||||
|
||||
public function __construct(
|
||||
protected Repository $config,
|
||||
) {
|
||||
}
|
||||
|
||||
public function needsTechnicalApproval(VacationType $type): bool
|
||||
{
|
||||
return $this->getConfigFor($type)[static::KEY_TECHNICAL_APPROVAL];
|
||||
}
|
||||
|
||||
public function needsAdministrativeApproval(VacationType $type): bool
|
||||
{
|
||||
return $this->getConfigFor($type)[static::KEY_ADMINISTRATIVE_APPROVAL];
|
||||
}
|
||||
|
||||
public function isBillable(VacationType $type): bool
|
||||
{
|
||||
return $this->getConfigFor($type)[static::KEY_BILLABLE];
|
||||
}
|
||||
|
||||
public function hasLimit(VacationType $type): bool
|
||||
{
|
||||
return $this->getConfigFor($type)[static::KEY_HAS_LIMIT];
|
||||
}
|
||||
|
||||
protected function getConfigFor(VacationType $type): array
|
||||
{
|
||||
return $this->config->get("vacation_types.{$type->value}");
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class ApprovedVacationDaysInSameRange implements VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||
{
|
||||
return $next($vacationRequest);
|
||||
}
|
||||
}
|
16
app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
Normal file
16
app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class DoesNotExceedLimitRule implements VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||
{
|
||||
return $next($vacationRequest);
|
||||
}
|
||||
}
|
16
app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
Normal file
16
app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class MinimumOneVacationDayRule implements VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||
{
|
||||
return $next($vacationRequest);
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class PendingVacationRequestInSameRange implements VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||
{
|
||||
return $next($vacationRequest);
|
||||
}
|
||||
}
|
16
app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
Normal file
16
app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class UsedVacationDaysInSameRange
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||
{
|
||||
return $next($vacationRequest);
|
||||
}
|
||||
}
|
13
app/Domain/Validation/Rules/VacationRequestRule.php
Normal file
13
app/Domain/Validation/Rules/VacationRequestRule.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation\Rules;
|
||||
|
||||
use Closure;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
interface VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest, Closure $next);
|
||||
}
|
35
app/Domain/Validation/VacationRequestValidator.php
Normal file
35
app/Domain/Validation/VacationRequestValidator.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Domain\Validation;
|
||||
|
||||
use Illuminate\Pipeline\Pipeline;
|
||||
use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange;
|
||||
use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule;
|
||||
use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule;
|
||||
use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestValidator
|
||||
{
|
||||
protected array $rules = [
|
||||
MinimumOneVacationDayRule::class,
|
||||
DoesNotExceedLimitRule::class,
|
||||
PendingVacationRequestInSameRange::class,
|
||||
ApprovedVacationDaysInSameRange::class,
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected Pipeline $pipeline,
|
||||
) {
|
||||
}
|
||||
|
||||
public function validate(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$this->pipeline
|
||||
->send($vacationRequest)
|
||||
->through($this->rules)
|
||||
->via("check");
|
||||
}
|
||||
}
|
@@ -13,7 +13,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Toby\Domain\EmploymentForm;
|
||||
use Toby\Domain\Enums\EmploymentForm;
|
||||
use Toby\Domain\Enums\Role;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@@ -21,9 +22,11 @@ use Toby\Domain\EmploymentForm;
|
||||
* @property string $last_name
|
||||
* @property string $email
|
||||
* @property string $avatar
|
||||
* @property Role $role
|
||||
* @property EmploymentForm $employment_form
|
||||
* @property Carbon $employment_date
|
||||
* @property Collection $vacationLimits
|
||||
* @property Collection $vacationRequests
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
@@ -34,6 +37,7 @@ class User extends Authenticatable
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"role" => Role::class,
|
||||
"employment_form" => EmploymentForm::class,
|
||||
"employment_date" => "date",
|
||||
];
|
||||
@@ -47,6 +51,11 @@ class User extends Authenticatable
|
||||
return $this->hasMany(VacationLimit::class);
|
||||
}
|
||||
|
||||
public function vacationRequests(): HasMany
|
||||
{
|
||||
return $this->hasMany(VacationRequest::class);
|
||||
}
|
||||
|
||||
public function scopeSearch(Builder $query, ?string $text): Builder
|
||||
{
|
||||
if ($text === null) {
|
||||
@@ -71,6 +80,11 @@ class User extends Authenticatable
|
||||
return "{$this->first_name} {$this->last_name}";
|
||||
}
|
||||
|
||||
public function hasRole(Role $role): bool
|
||||
{
|
||||
return $this->role === $role;
|
||||
}
|
||||
|
||||
protected static function newFactory(): UserFactory
|
||||
{
|
||||
return UserFactory::new();
|
||||
|
67
app/Eloquent/Models/VacationRequest.php
Normal file
67
app/Eloquent/Models/VacationRequest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Eloquent\Models;
|
||||
|
||||
use Database\Factories\VacationRequestFactory;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
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 Toby\Domain\Enums\VacationType;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property VacationType $type
|
||||
* @property VacationRequestState $state
|
||||
* @property Carbon $from
|
||||
* @property Carbon $to
|
||||
* @property string $comment
|
||||
* @property User $user
|
||||
* @property Collection $activities
|
||||
*/
|
||||
class VacationRequest extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"type" => VacationType::class,
|
||||
"state" => VacationRequestState::class,
|
||||
"from" => "date",
|
||||
"to" => "date",
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function activities(): HasMany
|
||||
{
|
||||
return $this->hasMany(VacationRequestActivity::class);
|
||||
}
|
||||
|
||||
public function changeStateTo(VacationRequestState $state): void
|
||||
{
|
||||
$this->state = $state;
|
||||
|
||||
$this->save();
|
||||
}
|
||||
|
||||
public function scopeStates(Builder $query, array $states): Builder
|
||||
{
|
||||
return $query->whereIn("state", $states);
|
||||
}
|
||||
|
||||
protected static function newFactory(): VacationRequestFactory
|
||||
{
|
||||
return VacationRequestFactory::new();
|
||||
}
|
||||
}
|
36
app/Eloquent/Models/VacationRequestActivity.php
Normal file
36
app/Eloquent/Models/VacationRequestActivity.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Eloquent\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Toby\Domain\Enums\VacationRequestState;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property VacationRequest $vacationRequest
|
||||
* @property ?User $user
|
||||
* @property ?VacationRequestState $from
|
||||
* @property VacationRequestState $to
|
||||
*/
|
||||
class VacationRequestActivity extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"from" => VacationRequestState::class,
|
||||
"to" => VacationRequestState::class,
|
||||
];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function vacationRequest(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(VacationRequest::class);
|
||||
}
|
||||
}
|
59
app/Eloquent/Observers/VacationRequestObserver.php
Normal file
59
app/Eloquent/Observers/VacationRequestObserver.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Eloquent\Observers;
|
||||
|
||||
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Toby\Domain\Enums\VacationRequestState;
|
||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class VacationRequestObserver
|
||||
{
|
||||
public function __construct(
|
||||
protected Auth $auth,
|
||||
protected Dispatcher $dispatcher,
|
||||
) {
|
||||
}
|
||||
|
||||
public function creating(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$year = $vacationRequest->from->year;
|
||||
|
||||
$vacationRequestNumber = $vacationRequest->user->vacationRequests()
|
||||
->whereYear("from", $year)
|
||||
->count() + 1;
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
@@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Controllers;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
use Toby\Domain\EmploymentForm;
|
||||
use Toby\Domain\Enums\EmploymentForm;
|
||||
use Toby\Domain\Enums\Role;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Infrastructure\Http\Requests\UserRequest;
|
||||
use Toby\Infrastructure\Http\Resources\UserFormDataResource;
|
||||
@@ -35,6 +36,7 @@ class UserController extends Controller
|
||||
{
|
||||
return inertia("Users/Create", [
|
||||
"employmentForms" => EmploymentForm::casesToSelect(),
|
||||
"roles" => Role::casesToSelect(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -52,6 +54,7 @@ class UserController extends Controller
|
||||
return inertia("Users/Edit", [
|
||||
"user" => new UserFormDataResource($user),
|
||||
"employmentForms" => EmploymentForm::casesToSelect(),
|
||||
"roles" => Role::casesToSelect(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Response;
|
||||
use Toby\Domain\Enums\VacationRequestState;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
use Toby\Domain\VacationRequestStateManager;
|
||||
use Toby\Domain\Validation\VacationRequestValidator;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
|
||||
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
|
||||
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
|
||||
|
||||
class VacationRequestController extends Controller
|
||||
{
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$vacationRequests = $request->user()
|
||||
->vacationRequests()
|
||||
->latest()
|
||||
->states(VacationRequestState::filterByStatus($request->query("status", "all")))
|
||||
->paginate();
|
||||
|
||||
return inertia("VacationRequest/Index", [
|
||||
"requests" => VacationRequestResource::collection($vacationRequests),
|
||||
"filters" => $request->only("status"),
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(VacationRequest $vacationRequest): Response
|
||||
{
|
||||
return inertia("VacationRequest/Show", [
|
||||
"request" => new VacationRequestResource($vacationRequest),
|
||||
"activities" => VacationRequestActivityResource::collection($vacationRequest->activities),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
return inertia("VacationRequest/Create", [
|
||||
"vacationTypes" => VacationType::casesToSelect(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(
|
||||
VacationRequestRequest $request,
|
||||
VacationRequestValidator $vacationRequestValidator,
|
||||
VacationRequestStateManager $stateManager,
|
||||
): RedirectResponse {
|
||||
/** @var VacationRequest $vacationRequest */
|
||||
$vacationRequest = $request->user()->vacationRequests()->make($request->data());
|
||||
|
||||
$vacationRequestValidator->validate($vacationRequest);
|
||||
|
||||
$vacationRequest->save();
|
||||
$stateManager->markAsCreated($vacationRequest);
|
||||
|
||||
return redirect()
|
||||
->route("vacation.requests.index");
|
||||
}
|
||||
|
||||
public function reject(
|
||||
VacationRequest $vacationRequest,
|
||||
VacationRequestStateManager $stateManager,
|
||||
): RedirectResponse {
|
||||
$stateManager->reject($vacationRequest);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function cancel(
|
||||
VacationRequest $vacationRequest,
|
||||
VacationRequestStateManager $stateManager,
|
||||
): RedirectResponse {
|
||||
$stateManager->cancel($vacationRequest);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function acceptAsTechnical(
|
||||
VacationRequest $vacationRequest,
|
||||
VacationRequestStateManager $stateManager,
|
||||
): RedirectResponse {
|
||||
$stateManager->acceptAsTechnical($vacationRequest);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
public function acceptAsAdministrative(
|
||||
VacationRequest $vacationRequest,
|
||||
VacationRequestStateManager $stateManager,
|
||||
): RedirectResponse {
|
||||
$stateManager->acceptAsAdministrative($vacationRequest);
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
}
|
@@ -23,6 +23,7 @@ use Illuminate\Routing\Middleware\ValidateSignature;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
|
||||
use Toby\Infrastructure\Http\Middleware\Authenticate;
|
||||
use Toby\Infrastructure\Http\Middleware\HandleInertiaRequests;
|
||||
use Toby\Infrastructure\Http\Middleware\RedirectIfAuthenticated;
|
||||
@@ -52,6 +53,7 @@ class Kernel extends HttpKernel
|
||||
HandleInertiaRequests::class,
|
||||
],
|
||||
"api" => [
|
||||
EnsureFrontendRequestsAreStateful::class,
|
||||
"throttle:api",
|
||||
SubstituteBindings::class,
|
||||
],
|
||||
|
@@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Requests;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Toby\Domain\EmploymentForm;
|
||||
use Toby\Domain\Enums\EmploymentForm;
|
||||
use Toby\Domain\Enums\Role;
|
||||
|
||||
class UserRequest extends FormRequest
|
||||
{
|
||||
@@ -17,6 +18,7 @@ class UserRequest extends FormRequest
|
||||
"firstName" => ["required", "min:3", "max:80"],
|
||||
"lastName" => ["required", "min:3", "max:80"],
|
||||
"email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)],
|
||||
"role" => ["required", new Enum(Role::class)],
|
||||
"employmentForm" => ["required", new Enum(EmploymentForm::class)],
|
||||
"employmentDate" => ["required", "date_format:Y-m-d"],
|
||||
];
|
||||
@@ -28,6 +30,7 @@ class UserRequest extends FormRequest
|
||||
"first_name" => $this->get("firstName"),
|
||||
"last_name" => $this->get("lastName"),
|
||||
"email" => $this->get("email"),
|
||||
"role" => $this->get("role"),
|
||||
"employment_form" => $this->get("employmentForm"),
|
||||
"employment_date" => $this->get("employmentDate"),
|
||||
];
|
||||
|
33
app/Infrastructure/Http/Requests/VacationRequestRequest.php
Normal file
33
app/Infrastructure/Http/Requests/VacationRequestRequest.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
use Toby\Infrastructure\Http\Rules\YearPeriodExists;
|
||||
|
||||
class VacationRequestRequest extends FormRequest
|
||||
{
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
"type" => ["required", new Enum(VacationType::class)],
|
||||
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||
"comment" => ["nullable"],
|
||||
];
|
||||
}
|
||||
|
||||
public function data(): array
|
||||
{
|
||||
return [
|
||||
"type" => $this->get("type"),
|
||||
"from" => $this->get("from"),
|
||||
"to" => $this->get("to"),
|
||||
"comment" => $this->get("comment"),
|
||||
];
|
||||
}
|
||||
}
|
@@ -15,6 +15,7 @@ class HolidayResource extends JsonResource
|
||||
return [
|
||||
"id" => $this->id,
|
||||
"name" => $this->name,
|
||||
"date" => $this->date->toDateString(),
|
||||
"displayDate" => $this->date->toDisplayString(),
|
||||
"dayOfWeek" => $this->date->dayName,
|
||||
];
|
||||
|
@@ -17,8 +17,9 @@ class UserFormDataResource extends JsonResource
|
||||
"firstName" => $this->first_name,
|
||||
"lastName" => $this->last_name,
|
||||
"email" => $this->email,
|
||||
"role" => $this->role,
|
||||
"employmentForm" => $this->employment_form,
|
||||
"employmentDate" => $this->employment_date,
|
||||
"employmentDate" => $this->employment_date->toDateString(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,7 @@ class UserResource extends JsonResource
|
||||
"id" => $this->id,
|
||||
"name" => $this->fullName,
|
||||
"email" => $this->email,
|
||||
"role" => "Human Resources Manager",
|
||||
"role" => $this->role->label(),
|
||||
"avatar" => asset($this->avatar),
|
||||
"deleted" => $this->trashed(),
|
||||
"employmentForm" => $this->employment_form->label(),
|
||||
|
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class VacationRequestActivityResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
"date" => $this->created_at->toDisplayString(),
|
||||
"who" => $this->user ? $this->user->fullName : __("System"),
|
||||
"to" => $this->to->label(),
|
||||
];
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Http\Resources;
|
||||
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
class VacationRequestResource extends JsonResource
|
||||
{
|
||||
public static $wrap = null;
|
||||
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
"id" => $this->id,
|
||||
"name" => $this->name,
|
||||
"user" => new UserResource($this->user),
|
||||
"type" => $this->type->label(),
|
||||
"state" => $this->state->label(),
|
||||
"from" => $this->from->toDisplayString(),
|
||||
"to" => $this->to->toDisplayString(),
|
||||
"comment" => $this->comment,
|
||||
];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user