- actions and notifications refactor #88

Merged
Baakoma merged 9 commits from actions into main 2022-03-21 15:29:20 +01:00
48 changed files with 539 additions and 1014 deletions

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,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));
}
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,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
{
$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", [
"title" => $title,
]))
->line(__("The vacation request :title has been created correctly in the :appName.", [
"title" => $title,
"appName" => $appName,
]))
->subject($this->buildSubject())
->line($this->buildDescription())
->line(__("Vacation type: :type", [
"type" => $type,
]))
@@ -68,4 +61,38 @@ class VacationRequestCreatedNotification extends Notification
]))
->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\VacationRequest;
class VacationRequestApprovedNotification extends Notification
class VacationRequestStatusChangedNotification extends Notification
{
use Queueable;
@@ -45,6 +45,7 @@ class VacationRequestApprovedNotification extends Notification
$user = $this->user->first_name;
$title = $this->vacationRequest->name;
$type = $this->vacationRequest->type->label();
$status = $this->vacationRequest->state->label();
$from = $this->vacationRequest->from->toDisplayString();
$to = $this->vacationRequest->to->toDisplayString();
$days = $this->vacationRequest->vacations()->count();
@@ -54,12 +55,14 @@ class VacationRequestApprovedNotification extends Notification
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title has been approved", [
->subject(__("Vacation request :title has been :status", [
"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,
"requester" => $requester,
"status" => $status,
]))
->line(__("Vacation 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\VacationRequest;
class VacationRequestWaitsForTechApprovalNotification extends Notification
class VacationRequestWaitsForApprovalNotification extends Notification
{
use Queueable;

View File

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

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

@@ -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
->vacationLimits()
->with("user")
->has("user")
->orderByUserField("last_name")
->orderByUserField("first_name")
->get();

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

@@ -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",
"technical_approver": "Techniczny akceptujący",
"administrative_approver": "Administracyjny akceptujący",
"created": "Utworzony",
"cancelled": "Anulowany",
"rejected": "Odrzucony",
"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",
"cancelled": "anulowany",
"rejected": "odrzucony",
"approved": "zatwierdzony",
"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.",
"Vacation limit has been exceeded.": "Limit urlopu został przekroczony.",
@@ -65,12 +60,8 @@
"Click here for details": "Kliknij, aby zobaczyć szczegóły",
"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ę.",
"Vacation request :title has been approved": "Wniosek urlopowy :title został zatwierdzony",
"The vacation request :title for user :requester has been approved.": "Wniosek urlopowy :title od użytkownika :requester został zatwierdzony.",
"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 :status": "Wniosek urlopowy :title został :status",
"The vacation request :title for user :requester has been :status.": "Wniosek urlopowy :title od użytkownika :requester został :status.",
"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."
}

View File

@@ -6,14 +6,11 @@ namespace Tests\Feature;
use Illuminate\Foundation\Testing\DatabaseMigrations;
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 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;
@@ -34,6 +31,9 @@ class VacationRequestTest extends FeatureTestCase
{
parent::setUp();
Bus::fake();
Notification::fake();
$this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class);
}
@@ -132,8 +132,6 @@ class VacationRequestTest extends FeatureTestCase
public function testUserCanCreateVacationRequestOnEmployeeBehalfAndSkipAcceptanceFlow(): void
{
Event::fake(VacationRequestApproved::class);
$creator = User::factory()->admin()->createQuietly();
$user = User::factory()->createQuietly();
@@ -172,8 +170,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 +186,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 +209,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 +227,7 @@ class VacationRequestTest extends FeatureTestCase
->for($currentYearPeriod)
->create();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([
"state" => WaitingForTechnical::class,
"type" => VacationType::Vacation,
@@ -243,10 +240,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,14 @@ use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
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\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\VacationRequestStateManager;
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod;
@@ -23,14 +26,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();
}
@@ -47,6 +46,9 @@ class VacationRequestNotificationTest extends TestCase
$administrativeApprover = User::factory([
"role" => Role::AdministrativeApprover,
])->createQuietly();
$admin = User::factory([
"role" => Role::Administrator,
])->createQuietly();
$currentYearPeriod = YearPeriod::current();
@@ -62,9 +64,47 @@ class VacationRequestNotificationTest extends TestCase
->for($currentYearPeriod)
->create();
$this->stateManager->waitForTechnical($vacationRequest);
$waitForTechApprovalAction = $this->app->make(WaitForTechApprovalAction::class);
Notification::assertSentTo($technicalApprover, VacationRequestWaitsForTechApprovalNotification::class);
Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForTechApprovalNotification::class);
$waitForTechApprovalAction->execute($vacationRequest);
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);
}
}

View File

@@ -6,6 +6,7 @@ namespace Tests\Unit;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Notification;
use Tests\TestCase;
use Tests\Traits\InteractsWithYearPeriods;
@@ -30,6 +31,7 @@ class VacationRequestStatesTest extends TestCase
parent::setUp();
Notification::fake();
Bus::fake();
$this->stateManager = $this->app->make(VacationRequestStateManager::class);