#126 - vacation request reminders
This commit is contained in:
parent
d60dc75f99
commit
dcce188419
@ -16,7 +16,6 @@ class ExceptionHandler extends Handler
|
||||
"password",
|
||||
"password_confirmation",
|
||||
];
|
||||
|
||||
protected array $handleByInertia = [
|
||||
Response::HTTP_INTERNAL_SERVER_ERROR,
|
||||
Response::HTTP_SERVICE_UNAVAILABLE,
|
||||
|
@ -99,4 +99,3 @@ class VacationRequestWaitsForApprovalNotification extends Notification
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,5 +9,6 @@ use Toby\Eloquent\Models\VacationRequest;
|
||||
interface VacationRequestRule
|
||||
{
|
||||
public function check(VacationRequest $vacationRequest): bool;
|
||||
|
||||
public function errorMessage(): string;
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ class Holiday extends Model
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"date" => "date",
|
||||
];
|
||||
|
@ -26,9 +26,7 @@ class Profile extends Model
|
||||
use HasAvatar;
|
||||
|
||||
protected $primaryKey = "user_id";
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"employment_form" => EmploymentForm::class,
|
||||
"employment_date" => "date",
|
||||
|
@ -33,18 +33,15 @@ class User extends Authenticatable
|
||||
use SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"role" => Role::class,
|
||||
"last_active_at" => "datetime",
|
||||
"employment_form" => EmploymentForm::class,
|
||||
"employment_date" => "date",
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
"remember_token",
|
||||
];
|
||||
|
||||
protected $with = [
|
||||
"profile",
|
||||
];
|
||||
|
@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\ModelStates\HasStates;
|
||||
@ -41,7 +42,6 @@ class VacationRequest extends Model
|
||||
use HasStates;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"type" => VacationType::class,
|
||||
"state" => VacationRequestState::class,
|
||||
@ -85,6 +85,13 @@ class VacationRequest extends Model
|
||||
return $query->whereNotState("state", $states);
|
||||
}
|
||||
|
||||
public function scopeType(Builder $query, VacationType|array $types): Builder
|
||||
{
|
||||
$types = Arr::wrap($types);
|
||||
|
||||
return $query->whereIn("type", $types);
|
||||
}
|
||||
|
||||
public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder
|
||||
{
|
||||
return $query->where("from", "<=", $vacationRequest->to)
|
||||
|
@ -22,7 +22,6 @@ class VacationRequestActivity extends Model
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
"from" => VacationRequestState::class,
|
||||
"to" => VacationRequestState::class,
|
||||
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Toby\Infrastructure\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Toby\Domain\Enums\Role;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification;
|
||||
use Toby\Domain\States\VacationRequest\WaitingForAdministrative;
|
||||
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
|
||||
use Toby\Domain\VacationTypeConfigRetriever;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
class SendVacationRequestRemindersToApprovers extends Command
|
||||
{
|
||||
public const REMINDER_INTERVAL = 3;
|
||||
|
||||
protected $signature = "toby:send-vacation-request-reminders";
|
||||
protected $description = "Sends vacation request reminders to approvers if they didn't approve";
|
||||
|
||||
public function handle(VacationTypeConfigRetriever $configRetriever): void
|
||||
{
|
||||
$vacationRequests = VacationRequest::query()
|
||||
->type(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))->all())
|
||||
->get();
|
||||
|
||||
/** @var VacationRequest $vacationRequest */
|
||||
foreach ($vacationRequests as $vacationRequest) {
|
||||
if (!$this->shouldNotify($vacationRequest)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($vacationRequest->state->equals(WaitingForTechnical::class)) {
|
||||
$this->notifyTechnicalApprovers($vacationRequest);
|
||||
}
|
||||
|
||||
if ($vacationRequest->state->equals(WaitingForAdministrative::class)) {
|
||||
$this->notifyAdminApprovers($vacationRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function shouldNotify(VacationRequest $vacationRequest): bool
|
||||
{
|
||||
$today = Carbon::today();
|
||||
$diff = $vacationRequest->updated_at->diffInDays($today);
|
||||
|
||||
return $diff >= static::REMINDER_INTERVAL && ($diff % static::REMINDER_INTERVAL === 0);
|
||||
}
|
||||
|
||||
protected function notifyAdminApprovers(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$users = User::query()
|
||||
->whereIn("role", [Role::AdministrativeApprover, Role::Administrator])
|
||||
->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user));
|
||||
}
|
||||
}
|
||||
|
||||
protected function notifyTechnicalApprovers(VacationRequest $vacationRequest): void
|
||||
{
|
||||
$users = User::query()
|
||||
->whereIn("role", [Role::TechnicalApprover, Role::Administrator])
|
||||
->get();
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user));
|
||||
}
|
||||
}
|
||||
}
|
@ -40,7 +40,6 @@ class Kernel extends HttpKernel
|
||||
TrimStrings::class,
|
||||
ConvertEmptyStringsToNull::class,
|
||||
];
|
||||
|
||||
protected $middlewareGroups = [
|
||||
"web" => [
|
||||
EncryptCookies::class,
|
||||
@ -58,7 +57,6 @@ class Kernel extends HttpKernel
|
||||
SubstituteBindings::class,
|
||||
],
|
||||
];
|
||||
|
||||
protected $routeMiddleware = [
|
||||
"auth" => Authenticate::class,
|
||||
"auth.basic" => AuthenticateWithBasicAuth::class,
|
||||
|
142
tests/Unit/SendVacationRequestRemindersTest.php
Normal file
142
tests/Unit/SendVacationRequestRemindersTest.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Tests\TestCase;
|
||||
use Tests\Traits\InteractsWithYearPeriods;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification;
|
||||
use Toby\Domain\States\VacationRequest\WaitingForAdministrative;
|
||||
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
use Toby\Eloquent\Models\YearPeriod;
|
||||
use Toby\Infrastructure\Console\Commands\SendVacationRequestRemindersToApprovers;
|
||||
|
||||
class SendVacationRequestRemindersTest extends TestCase
|
||||
{
|
||||
use DatabaseMigrations;
|
||||
use InteractsWithYearPeriods;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->createCurrentYearPeriod();
|
||||
|
||||
Notification::fake();
|
||||
}
|
||||
|
||||
public function testReminderIsSentIfItsBeenThreeDaysSinceTheUpdate(): void
|
||||
{
|
||||
$currentYearPeriod = YearPeriod::current();
|
||||
$now = Carbon::today();
|
||||
$this->travelTo($now);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$technicalApprover = User::factory()
|
||||
->technicalApprover()
|
||||
->create();
|
||||
|
||||
VacationRequest::factory([
|
||||
"type" => VacationType::Vacation->value,
|
||||
"state" => WaitingForTechnical::class,
|
||||
])
|
||||
->for($user)
|
||||
->for($currentYearPeriod)
|
||||
->create();
|
||||
|
||||
$this->travelTo($now->addDays(3));
|
||||
|
||||
$this->artisan(SendVacationRequestRemindersToApprovers::class);
|
||||
|
||||
Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class);
|
||||
}
|
||||
|
||||
public function testReminderIsSentIfItsBeenAnotherThreeDaysSinceTheUpdate(): void
|
||||
{
|
||||
$currentYearPeriod = YearPeriod::current();
|
||||
$now = Carbon::today();
|
||||
$this->travelTo($now);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$technicalApprover = User::factory()
|
||||
->technicalApprover()
|
||||
->create();
|
||||
|
||||
VacationRequest::factory([
|
||||
"type" => VacationType::Vacation->value,
|
||||
"state" => WaitingForTechnical::class,
|
||||
])
|
||||
->for($user)
|
||||
->for($currentYearPeriod)
|
||||
->create();
|
||||
|
||||
$this->travelTo($now->addDays(6));
|
||||
|
||||
$this->artisan(SendVacationRequestRemindersToApprovers::class);
|
||||
|
||||
Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class);
|
||||
}
|
||||
|
||||
public function testReminderIsNotSentIfItHasntBeenThreeDays(): void
|
||||
{
|
||||
$currentYearPeriod = YearPeriod::current();
|
||||
$now = Carbon::today();
|
||||
$this->travelTo($now);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$technicalApprover = User::factory()
|
||||
->technicalApprover()
|
||||
->create();
|
||||
|
||||
VacationRequest::factory([
|
||||
"type" => VacationType::Vacation->value,
|
||||
"state" => WaitingForTechnical::class,
|
||||
])
|
||||
->for($user)
|
||||
->for($currentYearPeriod)
|
||||
->create();
|
||||
|
||||
$this->travelTo($now->addDays(2));
|
||||
|
||||
$this->artisan(SendVacationRequestRemindersToApprovers::class);
|
||||
|
||||
Notification::assertNotSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class);
|
||||
}
|
||||
|
||||
public function testReminderIsSentToProperApprover(): void
|
||||
{
|
||||
$currentYearPeriod = YearPeriod::current();
|
||||
$now = Carbon::today();
|
||||
$this->travelTo($now);
|
||||
|
||||
$user = User::factory()->create();
|
||||
$adminApprover = User::factory()
|
||||
->administrativeApprover()
|
||||
->create();
|
||||
$technicalApprover = User::factory()
|
||||
->technicalApprover()
|
||||
->create();
|
||||
|
||||
VacationRequest::factory([
|
||||
"type" => VacationType::Vacation->value,
|
||||
"state" => WaitingForAdministrative::class,
|
||||
])
|
||||
->for($user)
|
||||
->for($currentYearPeriod)
|
||||
->create();
|
||||
|
||||
$this->travelTo($now->addDays(3));
|
||||
|
||||
$this->artisan(SendVacationRequestRemindersToApprovers::class);
|
||||
|
||||
Notification::assertSentTo([$adminApprover], VacationRequestWaitsForApprovalNotification::class);
|
||||
Notification::assertNotSentTo([$technicalApprover, $user], VacationRequestWaitsForApprovalNotification::class);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user