- actions and notifications refactor (#88)

* wip

* fix

* fix

* fix

* add test

* fix

* wip

* fix

* fix translations

Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
Adrian Hopek 2022-03-21 15:29:20 +01:00 committed by GitHub
parent d8ac2bd61f
commit 95f5ed44d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 537 additions and 1014 deletions

View File

@ -5,39 +5,8 @@ declare(strict_types=1);
namespace Toby\Architecture\Providers; namespace Toby\Architecture\Providers;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; 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\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\Listeners\CreateVacationRequestActivity;
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
use Toby\Domain\Listeners\HandleCancelledVacationRequest;
use Toby\Domain\Listeners\HandleCreatedVacationRequest;
use Toby\Domain\Listeners\SendApprovedVacationRequestNotification;
use Toby\Domain\Listeners\SendCancelledVacationRequestNotification;
use Toby\Domain\Listeners\SendCreatedVacationRequestNotification;
use Toby\Domain\Listeners\SendRejectedVacationRequestNotification;
use Toby\Domain\Listeners\SendWaitedForAdministrativeVacationRequestNotification;
use Toby\Domain\Listeners\SendWaitedForTechnicalVacationRequestNotification;
class EventServiceProvider extends ServiceProvider class EventServiceProvider extends ServiceProvider
{ {
protected $listen = [ protected $listen = [];
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
VacationRequestCreated::class => [HandleCreatedVacationRequest::class, SendCreatedVacationRequestNotification::class],
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
VacationRequestApproved::class => [HandleApprovedVacationRequest::class, SendApprovedVacationRequestNotification::class],
VacationRequestRejected::class => [SendRejectedVacationRequestNotification::class],
VacationRequestCancelled::class => [HandleCancelledVacationRequest::class, SendCancelledVacationRequestNotification::class],
VacationRequestWaitsForTechApproval::class => [SendWaitedForTechnicalVacationRequestNotification::class],
VacationRequestWaitsForAdminApproval::class => [SendWaitedForAdministrativeVacationRequestNotification::class],
];
} }

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class AcceptAsAdministrativeAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
protected ApproveAction $approveAction,
) {}
public function execute(VacationRequest $vacationRequest, User $user): void
{
$this->stateManager->acceptAsAdministrative($vacationRequest, $user);
$this->approveAction->execute($vacationRequest);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationTypeConfigRetriever;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class AcceptAsTechnicalAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
protected VacationTypeConfigRetriever $configRetriever,
protected WaitForAdminApprovalAction $waitForAdminApprovalAction,
protected ApproveAction $approveAction,
) {}
public function execute(VacationRequest $vacationRequest, User $user): void
{
$this->stateManager->acceptAsTechnical($vacationRequest, $user);
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
$this->waitForAdminApprovalAction->execute($vacationRequest);
return;
}
$this->approveAction->execute($vacationRequest);
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestStatusChangedNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar;
class ApproveAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
) {}
public function execute(VacationRequest $vacationRequest, ?User $user = null): void
{
$this->stateManager->approve($vacationRequest, $user);
SendVacationRequestDaysToGoogleCalendar::dispatch($vacationRequest);
$this->notify($vacationRequest);
}
protected function notify(VacationRequest $vacationRequest): void
{
$users = User::query()
->where("id", "!=", $vacationRequest->user->id)
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user));
}
}

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestStatusChangedNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Infrastructure\Jobs\ClearVacationRequestDaysInGoogleCalendar;
class CancelAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
) {}
public function execute(VacationRequest $vacationRequest, User $user): void
{
$this->stateManager->cancel($vacationRequest, $user);
ClearVacationRequestDaysInGoogleCalendar::dispatch($vacationRequest);
$this->notify($vacationRequest);
}
protected function notify(VacationRequest $vacationRequest): void
{
$users = User::query()
->where("id", "!=", $vacationRequest->user->id)
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user));
}
}

View File

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Illuminate\Validation\ValidationException;
use Toby\Domain\Notifications\VacationRequestCreatedNotification;
use Toby\Domain\VacationDaysCalculator;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationTypeConfigRetriever;
use Toby\Domain\Validation\VacationRequestValidator;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class CreateAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
protected VacationRequestValidator $vacationRequestValidator,
protected VacationTypeConfigRetriever $configRetriever,
protected VacationDaysCalculator $vacationDaysCalculator,
protected WaitForTechApprovalAction $waitForTechApprovalAction,
protected WaitForAdminApprovalAction $waitForAdminApprovalAction,
protected ApproveAction $approveAction,
) {}
/**
* @throws ValidationException
*/
public function execute(array $data, User $creator): VacationRequest
{
$vacationRequest = $this->createVacationRequest($data, $creator);
$this->handleCreatedVacationRequest($vacationRequest);
$this->notify($vacationRequest);
return $vacationRequest;
}
/**
* @throws ValidationException
*/
protected function createVacationRequest(array $data, User $creator): VacationRequest
{
/** @var VacationRequest $vacationRequest */
$vacationRequest = $creator->createdVacationRequests()->make($data);
$this->vacationRequestValidator->validate($vacationRequest);
$vacationRequest->save();
$days = $this->vacationDaysCalculator->calculateDays(
$vacationRequest->yearPeriod,
$vacationRequest->from,
$vacationRequest->to,
);
foreach ($days as $day) {
$vacationRequest->vacations()->create([
"date" => $day,
"user_id" => $vacationRequest->user->id,
"year_period_id" => $vacationRequest->yearPeriod->id,
]);
}
$this->stateManager->markAsCreated($vacationRequest);
return $vacationRequest;
}
protected function handleCreatedVacationRequest(VacationRequest $vacationRequest): void
{
if ($vacationRequest->hasFlowSkipped()) {
$this->approveAction->execute($vacationRequest);
return;
}
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
$this->waitForTechApprovalAction->execute($vacationRequest);
return;
}
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
$this->waitForAdminApprovalAction->execute($vacationRequest);
return;
}
$this->stateManager->approve($vacationRequest);
}
protected function notify(VacationRequest $vacationRequest): void
{
$vacationRequest->user->notify(new VacationRequestCreatedNotification($vacationRequest));
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestStatusChangedNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class RejectAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
) {}
public function execute(VacationRequest $vacationRequest, User $user): void
{
$this->stateManager->reject($vacationRequest, $user);
$this->notify($vacationRequest);
}
protected function notify(VacationRequest $vacationRequest): void
{
$users = User::query()
->where("id", "!=", $vacationRequest->user->id)
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user));
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationTypeConfigRetriever;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class WaitForAdminApprovalAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
protected VacationTypeConfigRetriever $configRetriever,
protected ApproveAction $approveAction,
) {}
public function execute(VacationRequest $vacationRequest): void
{
$this->stateManager->waitForAdministrative($vacationRequest);
$this->waitForAdminApprovers($vacationRequest);
}
protected function waitForAdminApprovers(VacationRequest $vacationRequest): void
{
$users = User::query()
->whereIn("role", [Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user));
}
}
}

View File

@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationTypeConfigRetriever;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class WaitForTechApprovalAction
{
public function __construct(
protected VacationRequestStateManager $stateManager,
protected VacationTypeConfigRetriever $configRetriever,
protected ApproveAction $approveAction,
) {}
public function execute(VacationRequest $vacationRequest): void
{
$this->stateManager->waitForTechnical($vacationRequest);
$this->notifyTechApprovers($vacationRequest);
}
protected function notifyTechApprovers(VacationRequest $vacationRequest): void
{
$users = User::query()
->whereIn("role", [Role::TechnicalApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user));
}
}
}

View File

@ -1,19 +0,0 @@
<?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,
) {}
}

View File

@ -1,19 +0,0 @@
<?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,
) {}
}

View File

@ -1,19 +0,0 @@
<?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,
) {}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestCancelled
{
use Dispatchable;
use SerializesModels;
public function __construct(
public VacationRequest $vacationRequest,
) {}
}

View File

@ -1,19 +0,0 @@
<?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,
) {}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestRejected
{
use Dispatchable;
use SerializesModels;
public function __construct(
public VacationRequest $vacationRequest,
) {}
}

View File

@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Toby\Domain\States\VacationRequest\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,
) {}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestWaitsForAdminApproval
{
use Dispatchable;
use SerializesModels;
public function __construct(
public VacationRequest $vacationRequest,
) {}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Events;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestWaitsForTechApproval
{
use Dispatchable;
use SerializesModels;
public function __construct(
public VacationRequest $vacationRequest,
) {}
}

View File

@ -1,19 +0,0 @@
<?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,
]);
}
}

View File

@ -1,20 +0,0 @@
<?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);
}
}

View File

@ -1,30 +0,0 @@
<?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);
}
}

View File

@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Toby\Domain\Events\VacationRequestApproved;
use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar;
class HandleApprovedVacationRequest
{
public function handle(VacationRequestApproved $event): void
{
SendVacationRequestDaysToGoogleCalendar::dispatch($event->vacationRequest);
}
}

View File

@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Toby\Domain\Events\VacationRequestCancelled;
use Toby\Infrastructure\Jobs\ClearVacationRequestDaysInGoogleCalendar;
class HandleCancelledVacationRequest
{
public function handle(VacationRequestCancelled $event): void
{
ClearVacationRequestDaysInGoogleCalendar::dispatch($event->vacationRequest);
}
}

View File

@ -1,42 +0,0 @@
<?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 ($vacationRequest->hasFlowSkipped()) {
$this->stateManager->approve($vacationRequest);
return;
}
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);
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Events\VacationRequestApproved;
use Toby\Domain\Notifications\VacationRequestApprovedNotification;
use Toby\Eloquent\Models\User;
class SendApprovedVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestApproved $event): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $user));
}
$event->vacationRequest->user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $event->vacationRequest->user));
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover])
->get();
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Events\VacationRequestCancelled;
use Toby\Domain\Notifications\VacationRequestCancelledNotification;
use Toby\Eloquent\Models\User;
class SendCancelledVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestCancelled $event): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $user));
}
$event->vacationRequest->user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $event->vacationRequest->user));
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover])
->get();
}
}

View File

@ -1,26 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Toby\Domain\Events\VacationRequestCreated;
use Toby\Domain\Notifications\VacationRequestCreatedNotification;
use Toby\Domain\Notifications\VacationRequestCreatedOnEmployeeBehalf;
class SendCreatedVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestCreated $event): void
{
$vacationRequest = $event->vacationRequest;
if ($vacationRequest->creator->is($vacationRequest->user)) {
$vacationRequest->user->notify(new VacationRequestCreatedNotification($vacationRequest));
} else {
$vacationRequest->user->notify(new VacationRequestCreatedOnEmployeeBehalf($vacationRequest));
}
}
}

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Events\VacationRequestRejected;
use Toby\Domain\Notifications\VacationRequestRejectedNotification;
use Toby\Eloquent\Models\User;
class SendRejectedVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestRejected $event): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $user));
}
$event->vacationRequest->user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $event->vacationRequest->user));
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover])
->get();
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Events\VacationRequestWaitsForAdminApproval;
use Toby\Domain\Notifications\VacationRequestWaitsForAdminApprovalNotification;
use Toby\Eloquent\Models\User;
class SendWaitedForAdministrativeVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestWaitsForAdminApproval $event): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestWaitsForAdminApprovalNotification($event->vacationRequest, $user));
}
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->where("role", [Role::AdministrativeApprover])
->get();
}
}

View File

@ -1,31 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Listeners;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Events\VacationRequestWaitsForTechApproval;
use Toby\Domain\Notifications\VacationRequestWaitsForTechApprovalNotification;
use Toby\Eloquent\Models\User;
class SendWaitedForTechnicalVacationRequestNotification
{
public function __construct(
) {}
public function handle(VacationRequestWaitsForTechApproval $event): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestWaitsForTechApprovalNotification($event->vacationRequest, $user));
}
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->where("role", [Role::TechnicalApprover])
->get();
}
}

View File

@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestCancelledNotification extends Notification
{
use Queueable;
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {}
public function via(): array
{
return ["mail"];
}
/**
* @throws InvalidArgumentException
*/
public function toMail(): MailMessage
{
$url = route(
"vacation.requests.show",
[
"vacationRequest" => $this->vacationRequest,
],
);
return $this->buildMailMessage($url);
}
protected function buildMailMessage(string $url): MailMessage
{
$user = $this->user->first_name;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label();
$from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count();
$requester = $this->vacationRequest->user->fullName;
return (new MailMessage())
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title has been cancelled", [
"title" => $title,
]))
->line(__("The vacation request :title for user :requester has been cancelled.", [
"title" => $title,
"requester" => $requester,
]))
->line(__("Vacation type: :type", [
"type" => $type,
]))
->line(__("From :from to :to (number of days: :days)", [
"from" => $from,
"to" => $to,
"days" => $days,
]))
->action(__("Click here for details"), $url);
}
}

View File

@ -40,24 +40,17 @@ class VacationRequestCreatedNotification extends Notification
protected function buildMailMessage(string $url): MailMessage protected function buildMailMessage(string $url): MailMessage
{ {
$user = $this->vacationRequest->user->first_name; $user = $this->vacationRequest->user->first_name;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label(); $type = $this->vacationRequest->type->label();
$from = $this->vacationRequest->from->toDisplayString(); $from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString(); $to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count(); $days = $this->vacationRequest->vacations()->count();
$appName = config("app.name");
return (new MailMessage()) return (new MailMessage())
->greeting(__("Hi :user!", [ ->greeting(__("Hi :user!", [
"user" => $user, "user" => $user,
])) ]))
->subject(__("Vacation request :title has been created", [ ->subject($this->buildSubject())
"title" => $title, ->line($this->buildDescription())
]))
->line(__("The vacation request :title has been created correctly in the :appName.", [
"title" => $title,
"appName" => $appName,
]))
->line(__("Vacation type: :type", [ ->line(__("Vacation type: :type", [
"type" => $type, "type" => $type,
])) ]))
@ -68,4 +61,38 @@ class VacationRequestCreatedNotification extends Notification
])) ]))
->action(__("Click here for details"), $url); ->action(__("Click here for details"), $url);
} }
protected function buildSubject(): string
{
$name = $this->vacationRequest->name;
if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) {
return __("Vacation request :title has been created", [
"title" => $name,
]);
}
return __("Vacation request :title has been created on your behalf", [
"title" => $name,
]);
}
protected function buildDescription(): string
{
$name = $this->vacationRequest->name;
$appName = config("app.name");
if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) {
return __("The vacation request :title has been created correctly in the :appName.", [
"title" => $name,
"appName" => $appName,
]);
}
return __("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [
"title" => $this->vacationRequest->name,
"appName" => $appName,
"creator" => $this->vacationRequest->creator->fullName,
]);
}
} }

View File

@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestCreatedOnEmployeeBehalf extends Notification
{
use Queueable;
public function __construct(
protected VacationRequest $vacationRequest,
) {}
public function via(): array
{
return ["mail"];
}
/**
* @throws InvalidArgumentException
*/
public function toMail(): MailMessage
{
$url = route(
"vacation.requests.show",
[
"vacationRequest" => $this->vacationRequest,
],
);
return $this->buildMailMessage($url);
}
protected function buildMailMessage(string $url): MailMessage
{
$creator = $this->vacationRequest->creator->fullName;
$user = $this->vacationRequest->user->first_name;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label();
$from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count();
$appName = config("app.name");
return (new MailMessage())
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title has been created on your behalf", [
"title" => $title,
]))
->line(__("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [
"title" => $title,
"appName" => $appName,
"creator" => $creator,
]))
->line(__("Vacation type: :type", [
"type" => $type,
]))
->line(__("From :from to :to (number of days: :days)", [
"from" => $from,
"to" => $to,
"days" => $days,
]))
->action(__("Click here for details"), $url);
}
}

View File

@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestRejectedNotification extends Notification
{
use Queueable;
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {}
public function via(): array
{
return ["mail"];
}
/**
* @throws InvalidArgumentException
*/
public function toMail(): MailMessage
{
$url = route(
"vacation.requests.show",
[
"vacationRequest" => $this->vacationRequest,
],
);
return $this->buildMailMessage($url);
}
protected function buildMailMessage(string $url): MailMessage
{
$user = $this->user->first_name;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label();
$from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count();
$requester = $this->vacationRequest->user->fullName;
return (new MailMessage())
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title has been rejected", [
"title" => $title,
]))
->line(__("The vacation request :title for user :requester has been rejected.", [
"title" => $title,
"requester" => $requester,
]))
->line(__("Vacation type: :type", [
"type" => $type,
]))
->line(__("From :from to :to (number of days: :days)", [
"from" => $from,
"to" => $to,
"days" => $days,
]))
->action(__("Click here for details"), $url);
}
}

View File

@ -11,7 +11,7 @@ use InvalidArgumentException;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
class VacationRequestApprovedNotification extends Notification class VacationRequestStatusChangedNotification extends Notification
{ {
use Queueable; use Queueable;
@ -45,6 +45,7 @@ class VacationRequestApprovedNotification extends Notification
$user = $this->user->first_name; $user = $this->user->first_name;
$title = $this->vacationRequest->name; $title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label(); $type = $this->vacationRequest->type->label();
$status = $this->vacationRequest->state->label();
$from = $this->vacationRequest->from->toDisplayString(); $from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString(); $to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count(); $days = $this->vacationRequest->vacations()->count();
@ -54,12 +55,14 @@ class VacationRequestApprovedNotification extends Notification
->greeting(__("Hi :user!", [ ->greeting(__("Hi :user!", [
"user" => $user, "user" => $user,
])) ]))
->subject(__("Vacation request :title has been approved", [ ->subject(__("Vacation request :title has been :status", [
"title" => $title, "title" => $title,
"status" => $status,
])) ]))
->line(__("The vacation request :title for user :requester has been approved.", [ ->line(__("The vacation request :title for user :requester has been :status.", [
"title" => $title, "title" => $title,
"requester" => $requester, "requester" => $requester,
"status" => $status,
])) ]))
->line(__("Vacation type: :type", [ ->line(__("Vacation type: :type", [
"type" => $type, "type" => $type,

View File

@ -1,74 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use InvalidArgumentException;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestWaitsForAdminApprovalNotification extends Notification
{
use Queueable;
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {}
public function via(): array
{
return ["mail"];
}
/**
* @throws InvalidArgumentException
*/
public function toMail(): MailMessage
{
$url = route(
"vacation.requests.show",
[
"vacationRequest" => $this->vacationRequest,
],
);
return $this->buildMailMessage($url);
}
protected function buildMailMessage(string $url): MailMessage
{
$user = $this->user->first_name;
$requester = $this->vacationRequest->user->fullName;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label();
$from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count();
return (new MailMessage())
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title is waiting for your approval", [
"title" => $title,
]))
->line(__("The vacation request :title from user: :requester is waiting for your approval.", [
"title" => $title,
"requester" => $requester,
]))
->line(__("Vacation type: :type", [
"type" => $type,
]))
->line(__("From :from to :to (number of days: :days)", [
"from" => $from,
"to" => $to,
"days" => $days,
]))
->action(__("Click here for details"), $url);
}
}

View File

@ -11,7 +11,7 @@ use InvalidArgumentException;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
class VacationRequestWaitsForTechApprovalNotification extends Notification class VacationRequestWaitsForApprovalNotification extends Notification
{ {
use Queueable; use Queueable;

View File

@ -36,4 +36,9 @@ abstract class VacationRequestState extends State
Approved::class, Approved::class,
], Cancelled::class); ], Cancelled::class);
} }
public function label(): string
{
return __(static::$name);
}
} }

View File

@ -4,17 +4,7 @@ declare(strict_types=1);
namespace Toby\Domain; namespace Toby\Domain;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Events\Dispatcher;
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\AcceptedByAdministrative;
use Toby\Domain\States\VacationRequest\AcceptedByTechnical; use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
use Toby\Domain\States\VacationRequest\Approved; use Toby\Domain\States\VacationRequest\Approved;
@ -29,63 +19,47 @@ use Toby\Eloquent\Models\VacationRequest;
class VacationRequestStateManager class VacationRequestStateManager
{ {
public function __construct( public function __construct(
protected Auth $auth,
protected Dispatcher $dispatcher, protected Dispatcher $dispatcher,
) {} ) {}
public function markAsCreated(VacationRequest $vacationRequest, ?User $user = null): void public function markAsCreated(VacationRequest $vacationRequest): void
{ {
$this->fireStateChangedEvent($vacationRequest, null, $vacationRequest->state, $user); $this->createActivity($vacationRequest, null, $vacationRequest->state, $vacationRequest->creator);
$this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
} }
public function approve(VacationRequest $vacationRequest, ?User $user = null): void public function approve(VacationRequest $vacationRequest, ?User $user = null): void
{ {
$this->changeState($vacationRequest, Approved::class, $user); $this->changeState($vacationRequest, Approved::class, $user);
$this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
} }
public function reject(VacationRequest $vacationRequest, ?User $user = null): void public function reject(VacationRequest $vacationRequest, User $user): void
{ {
$this->changeState($vacationRequest, Rejected::class, $user); $this->changeState($vacationRequest, Rejected::class, $user);
$this->dispatcher->dispatch(new VacationRequestRejected($vacationRequest));
} }
public function cancel(VacationRequest $vacationRequest, ?User $user = null): void public function cancel(VacationRequest $vacationRequest, User $user): void
{ {
$this->changeState($vacationRequest, Cancelled::class, $user); $this->changeState($vacationRequest, Cancelled::class, $user);
$this->dispatcher->dispatch(new VacationRequestCancelled($vacationRequest));
} }
public function acceptAsTechnical(VacationRequest $vacationRequest, ?User $user = null): void public function acceptAsTechnical(VacationRequest $vacationRequest, User $user): void
{ {
$this->changeState($vacationRequest, AcceptedByTechnical::class, $user); $this->changeState($vacationRequest, AcceptedByTechnical::class, $user);
$this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
} }
public function acceptAsAdministrative(VacationRequest $vacationRequest, ?User $user = null): void public function acceptAsAdministrative(VacationRequest $vacationRequest, User $user): void
{ {
$this->changeState($vacationRequest, AcceptedByAdministrative::class, $user); $this->changeState($vacationRequest, AcceptedByAdministrative::class, $user);
$this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
} }
public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void public function waitForTechnical(VacationRequest $vacationRequest): void
{ {
$this->changeState($vacationRequest, WaitingForTechnical::class, $user); $this->changeState($vacationRequest, WaitingForTechnical::class);
$this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest));
} }
public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void public function waitForAdministrative(VacationRequest $vacationRequest): void
{ {
$this->changeState($vacationRequest, WaitingForAdministrative::class, $user); $this->changeState($vacationRequest, WaitingForAdministrative::class);
$this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest));
} }
protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
@ -94,16 +68,19 @@ class VacationRequestStateManager
$vacationRequest->state->transitionTo($state); $vacationRequest->state->transitionTo($state);
$vacationRequest->save(); $vacationRequest->save();
$this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state, $user); $this->createActivity($vacationRequest, $previousState, $vacationRequest->state, $user);
} }
protected function fireStateChangedEvent( protected function createActivity(
VacationRequest $vacationRequest, VacationRequest $vacationRequest,
?VacationRequestState $from, ?VacationRequestState $from,
VacationRequestState $to, VacationRequestState $to,
?User $user = null, ?User $user = null,
): void { ): void {
$event = new VacationRequestStateChanged($vacationRequest, $from, $to, $user); $vacationRequest->activities()->create([
$this->dispatcher->dispatch($event); "from" => $from,
"to" => $to,
"user_id" => $user?->id,
]);
} }
} }

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Eloquent\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
class SelectedYearPeriodScope implements Scope
{
public function __construct(
protected YearPeriodRetriever $yearPeriodRetriever,
) {}
public function apply(Builder $builder, Model $model): Builder
{
return $builder->where("year_period_id", $this->yearPeriodRetriever->selected()->id);
}
}

View File

@ -1,25 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Console\Commands;
use Illuminate\Console\Command;
use Toby\Eloquent\Models\User;
class CreateUserCommand extends Command
{
protected $signature = "user:create {email : an email for the user}";
protected $description = "Creates a user";
public function handle(): void
{
$email = $this->argument("email");
User::factory([
"email" => $email,
])->create();
$this->info("The user has been created");
}
}

View File

@ -25,6 +25,7 @@ class VacationLimitController extends Controller
$limits = $yearPeriod $limits = $yearPeriod
->vacationLimits() ->vacationLimits()
->with("user") ->with("user")
->has("user")
->orderByUserField("last_name") ->orderByUserField("last_name")
->orderByUserField("first_name") ->orderByUserField("first_name")
->get(); ->get();

View File

@ -12,15 +12,17 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse; use Illuminate\Http\Response as LaravelResponse;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Inertia\Response; use Inertia\Response;
use Toby\Domain\Actions\VacationRequest\AcceptAsAdministrativeAction;
use Toby\Domain\Actions\VacationRequest\AcceptAsTechnicalAction;
use Toby\Domain\Actions\VacationRequest\CancelAction;
use Toby\Domain\Actions\VacationRequest\CreateAction;
use Toby\Domain\Actions\VacationRequest\RejectAction;
use Toby\Domain\Enums\VacationType; use Toby\Domain\Enums\VacationType;
use Toby\Domain\States\VacationRequest\AcceptedByAdministrative; use Toby\Domain\States\VacationRequest\AcceptedByAdministrative;
use Toby\Domain\States\VacationRequest\AcceptedByTechnical; use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
use Toby\Domain\States\VacationRequest\Cancelled; use Toby\Domain\States\VacationRequest\Cancelled;
use Toby\Domain\States\VacationRequest\Rejected; use Toby\Domain\States\VacationRequest\Rejected;
use Toby\Domain\VacationDaysCalculator;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationRequestStatesRetriever; use Toby\Domain\VacationRequestStatesRetriever;
use Toby\Domain\Validation\VacationRequestValidator;
use Toby\Eloquent\Helpers\YearPeriodRetriever; use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
@ -91,7 +93,12 @@ class VacationRequestController extends Controller
->with(["user", "vacations"]) ->with(["user", "vacations"])
->where("year_period_id", $yearPeriod->id) ->where("year_period_id", $yearPeriod->id)
->when($user !== null, fn(Builder $query) => $query->where("user_id", $user)) ->when($user !== null, fn(Builder $query) => $query->where("user_id", $user))
->when($status !== null, fn(Builder $query) => $query->states(VacationRequestStatesRetriever::filterByStatusGroup($status, $request->user()))) ->when(
$status !== null,
fn(Builder $query) => $query->states(
VacationRequestStatesRetriever::filterByStatusGroup($status, $request->user()),
),
)
->latest() ->latest()
->paginate(); ->paginate();
@ -179,12 +186,8 @@ class VacationRequestController extends Controller
* @throws AuthorizationException * @throws AuthorizationException
* @throws ValidationException * @throws ValidationException
*/ */
public function store( public function store(VacationRequestRequest $request, CreateAction $createAction): RedirectResponse
VacationRequestRequest $request, {
VacationRequestValidator $vacationRequestValidator,
VacationRequestStateManager $stateManager,
VacationDaysCalculator $vacationDaysCalculator,
): RedirectResponse {
if ($request->createsOnBehalfOfEmployee()) { if ($request->createsOnBehalfOfEmployee()) {
$this->authorize("createOnBehalfOfEmployee", VacationRequest::class); $this->authorize("createOnBehalfOfEmployee", VacationRequest::class);
} }
@ -193,27 +196,7 @@ class VacationRequestController extends Controller
$this->authorize("skipFlow", VacationRequest::class); $this->authorize("skipFlow", VacationRequest::class);
} }
/** @var VacationRequest $vacationRequest */ $vacationRequest = $createAction->execute($request->data(), $request->user());
$vacationRequest = $request->user()->createdVacationRequests()->make($request->data());
$vacationRequestValidator->validate($vacationRequest);
$vacationRequest->save();
$days = $vacationDaysCalculator->calculateDays(
$vacationRequest->yearPeriod,
$vacationRequest->from,
$vacationRequest->to,
);
foreach ($days as $day) {
$vacationRequest->vacations()->create([
"date" => $day,
"user_id" => $vacationRequest->user->id,
"year_period_id" => $vacationRequest->yearPeriod->id,
]);
}
$stateManager->markAsCreated($vacationRequest, $request->user());
return redirect() return redirect()
->route("vacation.requests.show", $vacationRequest) ->route("vacation.requests.show", $vacationRequest)
@ -226,11 +209,11 @@ class VacationRequestController extends Controller
public function reject( public function reject(
Request $request, Request $request,
VacationRequest $vacationRequest, VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager, RejectAction $rejectAction,
): RedirectResponse { ): RedirectResponse {
$this->authorize("reject", $vacationRequest); $this->authorize("reject", $vacationRequest);
$stateManager->reject($vacationRequest, $request->user()); $rejectAction->execute($vacationRequest, $request->user());
return redirect()->back() return redirect()->back()
->with("success", __("Vacation request has been rejected.")); ->with("success", __("Vacation request has been rejected."));
@ -242,11 +225,11 @@ class VacationRequestController extends Controller
public function cancel( public function cancel(
Request $request, Request $request,
VacationRequest $vacationRequest, VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager, CancelAction $cancelAction,
): RedirectResponse { ): RedirectResponse {
$this->authorize("cancel", $vacationRequest); $this->authorize("cancel", $vacationRequest);
$stateManager->cancel($vacationRequest, $request->user()); $cancelAction->execute($vacationRequest, $request->user());
return redirect()->back() return redirect()->back()
->with("success", __("Vacation request has been cancelled.")); ->with("success", __("Vacation request has been cancelled."));
@ -258,11 +241,11 @@ class VacationRequestController extends Controller
public function acceptAsTechnical( public function acceptAsTechnical(
Request $request, Request $request,
VacationRequest $vacationRequest, VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager, AcceptAsTechnicalAction $acceptAsTechnicalAction,
): RedirectResponse { ): RedirectResponse {
$this->authorize("acceptAsTechApprover", $vacationRequest); $this->authorize("acceptAsTechApprover", $vacationRequest);
$stateManager->acceptAsTechnical($vacationRequest, $request->user()); $acceptAsTechnicalAction->execute($vacationRequest, $request->user());
return redirect()->back() return redirect()->back()
->with("success", __("Vacation request has been accepted.")); ->with("success", __("Vacation request has been accepted."));
@ -274,11 +257,11 @@ class VacationRequestController extends Controller
public function acceptAsAdministrative( public function acceptAsAdministrative(
Request $request, Request $request,
VacationRequest $vacationRequest, VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager, AcceptAsAdministrativeAction $acceptAsAdministrativeAction,
): RedirectResponse { ): RedirectResponse {
$this->authorize("acceptAsAdminApprover", $vacationRequest); $this->authorize("acceptAsAdminApprover", $vacationRequest);
$stateManager->acceptAsAdministrative($vacationRequest, $request->user()); $acceptAsAdministrativeAction->execute($vacationRequest, $request->user());
return redirect()->back() return redirect()->back()
->with("success", __("Vacation request has been accepted.")); ->with("success", __("Vacation request has been accepted."));

View File

@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
public function up(): void
{
Schema::table("users", function (Blueprint $table): void {
$table->dropColumn("avatar");
});
}
public function down(): void
{
Schema::table("users", function (Blueprint $table): void {
$table->string("avatar")->nullable();
});
}
};

View File

@ -18,14 +18,9 @@
"administrator": "Administrator", "administrator": "Administrator",
"technical_approver": "Techniczny akceptujący", "technical_approver": "Techniczny akceptujący",
"administrative_approver": "Administracyjny akceptujący", "administrative_approver": "Administracyjny akceptujący",
"created": "Utworzony", "cancelled": "anulowany",
"cancelled": "Anulowany", "rejected": "odrzucony",
"rejected": "Odrzucony", "approved": "zatwierdzony",
"approved": "Zatwierdzony",
"waiting_for_technical": "Czeka na akceptację od technicznego",
"waiting_for_administrative": "Czeka na akceptację od administracyjnego",
"accepted_by_technical": "Zaakceptowany przez technicznego",
"accepted_by_administrative": "Zaakceptowany przez administracyjnego",
"You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.", "You have pending vacation request in this range.": "Masz oczekujący wniosek urlopowy w tym zakresie dat.",
"You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.", "You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.",
"Vacation limit has been exceeded.": "Limit urlopu został przekroczony.", "Vacation limit has been exceeded.": "Limit urlopu został przekroczony.",
@ -65,12 +60,8 @@
"Click here for details": "Kliknij, aby zobaczyć szczegóły", "Click here for details": "Kliknij, aby zobaczyć szczegóły",
"Vacation request :title is waiting for your approval": "Wniosek urlopowy :title czeka na zaakceptowanie", "Vacation request :title is waiting for your approval": "Wniosek urlopowy :title czeka na zaakceptowanie",
"The vacation request :title from user: :requester is waiting for your approval.": "Wniosek urlopowy :title od użytkownika :requester czeka na Twoją akceptację.", "The vacation request :title from user: :requester is waiting for your approval.": "Wniosek urlopowy :title od użytkownika :requester czeka na Twoją akceptację.",
"Vacation request :title has been approved": "Wniosek urlopowy :title został zatwierdzony", "Vacation request :title has been :status": "Wniosek urlopowy :title został :status",
"The vacation request :title for user :requester has been approved.": "Wniosek urlopowy :title od użytkownika :requester został zatwierdzony.", "The vacation request :title for user :requester has been :status.": "Wniosek urlopowy :title od użytkownika :requester został :status.",
"Vacation request :title has been cancelled": "Wniosek urlopowy :title został anulowany",
"The vacation request :title for user :requester has been cancelled.": "Wniosek urlopowy :title od użytkownika :requester został anulowany.",
"Vacation request :title has been rejected": "Wniosek urlopowy :title został odrzucony",
"The vacation request :title for user :requester has been rejected.": "Wniosek urlopowy :title od użytkownika :requester został odrzucony.",
"Vacation request :title has been created on your behalf": "Wniosek urlopowy :title został utworzony w Twoim imieniu", "Vacation request :title has been created on your behalf": "Wniosek urlopowy :title został utworzony w Twoim imieniu",
"The vacation request :title has been created correctly by user :creator on your behalf in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title w Twoim imieniu przez użytkownika :creator." "The vacation request :title has been created correctly by user :creator on your behalf in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title w Twoim imieniu przez użytkownika :creator."
} }

View File

@ -6,14 +6,11 @@ namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Carbon; use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Notification;
use Inertia\Testing\AssertableInertia as Assert; use Inertia\Testing\AssertableInertia as Assert;
use Tests\FeatureTestCase; use Tests\FeatureTestCase;
use Toby\Domain\Enums\VacationType; use Toby\Domain\Enums\VacationType;
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
use Toby\Domain\Events\VacationRequestApproved;
use Toby\Domain\Events\VacationRequestRejected;
use Toby\Domain\PolishHolidaysRetriever; use Toby\Domain\PolishHolidaysRetriever;
use Toby\Domain\States\VacationRequest\Approved; use Toby\Domain\States\VacationRequest\Approved;
use Toby\Domain\States\VacationRequest\Rejected; use Toby\Domain\States\VacationRequest\Rejected;
@ -34,6 +31,9 @@ class VacationRequestTest extends FeatureTestCase
{ {
parent::setUp(); parent::setUp();
Bus::fake();
Notification::fake();
$this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class); $this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class);
} }
@ -130,8 +130,6 @@ class VacationRequestTest extends FeatureTestCase
public function testUserCanCreateVacationRequestOnEmployeeBehalfAndSkipAcceptanceFlow(): void public function testUserCanCreateVacationRequestOnEmployeeBehalfAndSkipAcceptanceFlow(): void
{ {
Event::fake(VacationRequestApproved::class);
$creator = User::factory()->admin()->createQuietly(); $creator = User::factory()->admin()->createQuietly();
$user = User::factory()->createQuietly(); $user = User::factory()->createQuietly();
@ -169,8 +167,6 @@ class VacationRequestTest extends FeatureTestCase
public function testTechnicalApproverCanApproveVacationRequest(): void public function testTechnicalApproverCanApproveVacationRequest(): void
{ {
Event::fake(VacationRequestAcceptedByTechnical::class);
$user = User::factory()->createQuietly(); $user = User::factory()->createQuietly();
$technicalApprover = User::factory()->technicalApprover()->createQuietly(); $technicalApprover = User::factory()->technicalApprover()->createQuietly();
$currentYearPeriod = YearPeriod::current(); $currentYearPeriod = YearPeriod::current();
@ -187,13 +183,13 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical") ->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical")
->assertSessionHasNoErrors(); ->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestAcceptedByTechnical::class); $vacationRequest->refresh();
$this->assertTrue($vacationRequest->state->equals(WaitingForAdministrative::class));
} }
public function testAdministrativeApproverCanApproveVacationRequest(): void public function testAdministrativeApproverCanApproveVacationRequest(): void
{ {
Event::fake(VacationRequestAcceptedByAdministrative::class);
$user = User::factory()->createQuietly(); $user = User::factory()->createQuietly();
$administrativeApprover = User::factory()->administrativeApprover()->createQuietly(); $administrativeApprover = User::factory()->administrativeApprover()->createQuietly();
@ -210,13 +206,13 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative") ->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative")
->assertSessionHasNoErrors(); ->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestAcceptedByAdministrative::class); $vacationRequest->refresh();
$this->assertTrue($vacationRequest->state->equals(Approved::class));
} }
public function testTechnicalApproverCanRejectVacationRequest(): void public function testTechnicalApproverCanRejectVacationRequest(): void
{ {
Event::fake(VacationRequestRejected::class);
$user = User::factory()->createQuietly(); $user = User::factory()->createQuietly();
$technicalApprover = User::factory()->technicalApprover()->createQuietly(); $technicalApprover = User::factory()->technicalApprover()->createQuietly();
$currentYearPeriod = YearPeriod::current(); $currentYearPeriod = YearPeriod::current();
@ -228,6 +224,7 @@ class VacationRequestTest extends FeatureTestCase
->for($currentYearPeriod) ->for($currentYearPeriod)
->create(); ->create();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([ $vacationRequest = VacationRequest::factory([
"state" => WaitingForTechnical::class, "state" => WaitingForTechnical::class,
"type" => VacationType::Vacation, "type" => VacationType::Vacation,
@ -240,10 +237,9 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/reject") ->post("/vacation-requests/{$vacationRequest->id}/reject")
->assertSessionHasNoErrors(); ->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestRejected::class); $vacationRequest->refresh();
$this->assertDatabaseHas("vacation_requests", [
"state" => Rejected::$name, $this->assertTrue($vacationRequest->state->equals(Rejected::class));
]);
} }
public function testUserCannotCreateVacationRequestIfHeExceedsHisVacationLimit(): void public function testUserCannotCreateVacationRequestIfHeExceedsHisVacationLimit(): void

View File

@ -9,11 +9,14 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Notification;
use Tests\TestCase; use Tests\TestCase;
use Tests\Traits\InteractsWithYearPeriods; use Tests\Traits\InteractsWithYearPeriods;
use Toby\Domain\Actions\VacationRequest\RejectAction;
use Toby\Domain\Actions\VacationRequest\WaitForTechApprovalAction;
use Toby\Domain\Enums\Role; use Toby\Domain\Enums\Role;
use Toby\Domain\Enums\VacationType; use Toby\Domain\Enums\VacationType;
use Toby\Domain\Notifications\VacationRequestWaitsForTechApprovalNotification; use Toby\Domain\Notifications\VacationRequestStatusChangedNotification;
use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification;
use Toby\Domain\States\VacationRequest\Created; use Toby\Domain\States\VacationRequest\Created;
use Toby\Domain\VacationRequestStateManager; use Toby\Domain\States\VacationRequest\WaitingForTechnical;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod; use Toby\Eloquent\Models\YearPeriod;
@ -23,14 +26,10 @@ class VacationRequestNotificationTest extends TestCase
use DatabaseMigrations; use DatabaseMigrations;
use InteractsWithYearPeriods; use InteractsWithYearPeriods;
protected VacationRequestStateManager $stateManager;
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
$this->createCurrentYearPeriod(); $this->createCurrentYearPeriod();
} }
@ -47,6 +46,9 @@ class VacationRequestNotificationTest extends TestCase
$administrativeApprover = User::factory([ $administrativeApprover = User::factory([
"role" => Role::AdministrativeApprover, "role" => Role::AdministrativeApprover,
])->createQuietly(); ])->createQuietly();
$admin = User::factory([
"role" => Role::Administrator,
])->createQuietly();
$currentYearPeriod = YearPeriod::current(); $currentYearPeriod = YearPeriod::current();
@ -62,9 +64,47 @@ class VacationRequestNotificationTest extends TestCase
->for($currentYearPeriod) ->for($currentYearPeriod)
->create(); ->create();
$this->stateManager->waitForTechnical($vacationRequest); $waitForTechApprovalAction = $this->app->make(WaitForTechApprovalAction::class);
Notification::assertSentTo($technicalApprover, VacationRequestWaitsForTechApprovalNotification::class); $waitForTechApprovalAction->execute($vacationRequest);
Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForTechApprovalNotification::class);
Notification::assertSentTo([$technicalApprover, $admin], VacationRequestWaitsForApprovalNotification::class);
Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForApprovalNotification::class);
}
public function testNotificationIsSentOnceToUser(): void
{
Notification::fake();
$technicalApprover = User::factory([
"role" => Role::TechnicalApprover,
])->createQuietly();
$administrativeApprover = User::factory([
"role" => Role::AdministrativeApprover,
])->createQuietly();
$admin = User::factory([
"role" => Role::Administrator,
])->createQuietly();
$currentYearPeriod = YearPeriod::current();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([
"type" => VacationType::Vacation->value,
"state" => WaitingForTechnical::class,
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
"comment" => "Comment for the vacation request.",
])
->for($administrativeApprover)
->for($currentYearPeriod)
->create();
$rejectAction = $this->app->make(RejectAction::class);
$rejectAction->execute($vacationRequest, $technicalApprover);
Notification::assertSentTo([$technicalApprover, $admin, $administrativeApprover], VacationRequestStatusChangedNotification::class);
Notification::assertTimesSent(3, VacationRequestStatusChangedNotification::class);
} }
} }