- actions and notifications refactor #88

Merged
Baakoma merged 9 commits from actions into main 2022-03-21 15:29:20 +01:00
34 changed files with 400 additions and 642 deletions
Showing only changes of commit 70f73da784 - Show all commits

View File

@@ -5,39 +5,8 @@ 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\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
{
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],
];
protected $listen = [];
}

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,41 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestApprovedNotification;
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()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestApprovedNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestApprovedNotification($vacationRequest, $vacationRequest->user));
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestCancelledNotification;
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()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestCancelledNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestCancelledNotification($vacationRequest, $vacationRequest->user));
}
}
kamilpiech97 commented 2022-03-21 11:48:44 +01:00 (Migrated from github.com)
Review

Some actions have similar notify method, maybe we can move that into service or notify action.

Some actions have similar notify method, maybe we can move that into service or notify action.
Baakoma commented 2022-03-21 11:55:06 +01:00 (Migrated from github.com)
Review

I'm not sure if it's a good idea for now. There are only 3 places like this and they may change in different ways in the future.

I'm not sure if it's a good idea for now. There are only 3 places like this and they may change in different ways in the future.

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Illuminate\Validation\ValidationException;
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);
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);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Toby\Domain\Actions\VacationRequest;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestRejectedNotification;
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()
->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator])
->get();
foreach ($users as $user) {
$user->notify(new VacationRequestRejectedNotification($vacationRequest, $user));
}
$vacationRequest->user->notify(new VacationRequestRejectedNotification($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\VacationRequestWaitsForAdminApprovalNotification;
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 VacationRequestWaitsForAdminApprovalNotification($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\VacationRequestWaitsForTechApprovalNotification;
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 VacationRequestWaitsForTechApprovalNotification($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

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

View File

@@ -12,15 +12,17 @@ use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
use Illuminate\Validation\ValidationException;
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\States\VacationRequest\AcceptedByAdministrative;
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
use Toby\Domain\States\VacationRequest\Cancelled;
use Toby\Domain\States\VacationRequest\Rejected;
use Toby\Domain\VacationDaysCalculator;
use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\VacationRequestStatesRetriever;
use Toby\Domain\Validation\VacationRequestValidator;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
@@ -91,7 +93,12 @@ class VacationRequestController extends Controller
->with(["user", "vacations"])
->where("year_period_id", $yearPeriod->id)
->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()
->paginate();
@@ -179,12 +186,8 @@ class VacationRequestController extends Controller
* @throws AuthorizationException
* @throws ValidationException
*/
public function store(
VacationRequestRequest $request,
VacationRequestValidator $vacationRequestValidator,
VacationRequestStateManager $stateManager,
VacationDaysCalculator $vacationDaysCalculator,
): RedirectResponse {
public function store(VacationRequestRequest $request, CreateAction $createAction): RedirectResponse
{
if ($request->createsOnBehalfOfEmployee()) {
$this->authorize("createOnBehalfOfEmployee", VacationRequest::class);
}
@@ -193,27 +196,7 @@ class VacationRequestController extends Controller
$this->authorize("skipFlow", VacationRequest::class);
}
/** @var VacationRequest $vacationRequest */
$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());
$vacationRequest = $createAction->execute($request->data(), $request->user());
return redirect()
->route("vacation.requests.show", $vacationRequest)
@@ -226,11 +209,11 @@ class VacationRequestController extends Controller
public function reject(
Request $request,
VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager,
RejectAction $rejectAction,
): RedirectResponse {
$this->authorize("reject", $vacationRequest);
$stateManager->reject($vacationRequest, $request->user());
$rejectAction->execute($vacationRequest, $request->user());
return redirect()->back()
->with("success", __("Vacation request has been rejected."));
@@ -242,11 +225,11 @@ class VacationRequestController extends Controller
public function cancel(
Request $request,
VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager,
CancelAction $cancelAction,
): RedirectResponse {
$this->authorize("cancel", $vacationRequest);
$stateManager->cancel($vacationRequest, $request->user());
$cancelAction->execute($vacationRequest, $request->user());
return redirect()->back()
->with("success", __("Vacation request has been cancelled."));
@@ -258,11 +241,11 @@ class VacationRequestController extends Controller
public function acceptAsTechnical(
Request $request,
VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager,
AcceptAsTechnicalAction $acceptAsTechnicalAction,
): RedirectResponse {
$this->authorize("acceptAsTechApprover", $vacationRequest);
$stateManager->acceptAsTechnical($vacationRequest, $request->user());
$acceptAsTechnicalAction->execute($vacationRequest, $request->user());
return redirect()->back()
->with("success", __("Vacation request has been accepted."));
@@ -274,11 +257,11 @@ class VacationRequestController extends Controller
public function acceptAsAdministrative(
Request $request,
VacationRequest $vacationRequest,
VacationRequestStateManager $stateManager,
AcceptAsAdministrativeAction $acceptAsAdministrativeAction,
): RedirectResponse {
$this->authorize("acceptAsAdminApprover", $vacationRequest);
$stateManager->acceptAsAdministrative($vacationRequest, $request->user());
$acceptAsAdministrativeAction->execute($vacationRequest, $request->user());
return redirect()->back()
->with("success", __("Vacation request has been accepted."));

View File

@@ -6,14 +6,9 @@ namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\FeatureTestCase;
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\States\VacationRequest\Approved;
use Toby\Domain\States\VacationRequest\Rejected;
@@ -132,8 +127,6 @@ class VacationRequestTest extends FeatureTestCase
public function testUserCanCreateVacationRequestOnEmployeeBehalfAndSkipAcceptanceFlow(): void
{
Event::fake(VacationRequestApproved::class);
$creator = User::factory()->admin()->createQuietly();
$user = User::factory()->createQuietly();
@@ -172,8 +165,6 @@ class VacationRequestTest extends FeatureTestCase
public function testTechnicalApproverCanApproveVacationRequest(): void
{
Event::fake(VacationRequestAcceptedByTechnical::class);
$user = User::factory()->createQuietly();
$technicalApprover = User::factory()->technicalApprover()->createQuietly();
$currentYearPeriod = YearPeriod::current();
@@ -190,13 +181,13 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical")
->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestAcceptedByTechnical::class);
$vacationRequest->refresh();
$this->assertTrue($vacationRequest->state->equals(WaitingForAdministrative::class));
}
public function testAdministrativeApproverCanApproveVacationRequest(): void
{
Event::fake(VacationRequestAcceptedByAdministrative::class);
$user = User::factory()->createQuietly();
$administrativeApprover = User::factory()->administrativeApprover()->createQuietly();
@@ -213,13 +204,13 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative")
->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestAcceptedByAdministrative::class);
$vacationRequest->refresh();
$this->assertTrue($vacationRequest->state->equals(Approved::class));
}
public function testTechnicalApproverCanRejectVacationRequest(): void
{
Event::fake(VacationRequestRejected::class);
$user = User::factory()->createQuietly();
$technicalApprover = User::factory()->technicalApprover()->createQuietly();
$currentYearPeriod = YearPeriod::current();
@@ -231,6 +222,7 @@ class VacationRequestTest extends FeatureTestCase
->for($currentYearPeriod)
->create();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([
"state" => WaitingForTechnical::class,
"type" => VacationType::Vacation,
@@ -243,10 +235,9 @@ class VacationRequestTest extends FeatureTestCase
->post("/vacation-requests/{$vacationRequest->id}/reject")
->assertSessionHasNoErrors();
Event::assertDispatched(VacationRequestRejected::class);
$this->assertDatabaseHas("vacation_requests", [
"state" => Rejected::$name,
]);
$vacationRequest->refresh();
$this->assertTrue($vacationRequest->state->equals(Rejected::class));
}
public function testUserCannotCreateVacationRequestIfHeExceedsHisVacationLimit(): void

View File

@@ -9,11 +9,11 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
use Tests\Traits\InteractsWithYearPeriods;
use Toby\Domain\Actions\VacationRequest\WaitForTechApprovalAction;
use Toby\Domain\Enums\Role;
use Toby\Domain\Enums\VacationType;
use Toby\Domain\Notifications\VacationRequestWaitsForTechApprovalNotification;
use Toby\Domain\States\VacationRequest\Created;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod;
@@ -23,14 +23,10 @@ class VacationRequestNotificationTest extends TestCase
use DatabaseMigrations;
use InteractsWithYearPeriods;
protected VacationRequestStateManager $stateManager;
protected function setUp(): void
{
parent::setUp();
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
$this->createCurrentYearPeriod();
}
@@ -62,7 +58,9 @@ class VacationRequestNotificationTest extends TestCase
->for($currentYearPeriod)
->create();
$this->stateManager->waitForTechnical($vacationRequest);
$waitForTechApprovalAction = $this->app->make(WaitForTechApprovalAction::class);
$waitForTechApprovalAction->execute($vacationRequest);
Notification::assertSentTo($technicalApprover, VacationRequestWaitsForTechApprovalNotification::class);
Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForTechApprovalNotification::class);