#126 - vacation request reminders
This commit is contained in:
		@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user