Skip to content

Commit

Permalink
#177 - Delegations (#349)
Browse files Browse the repository at this point in the history
* #177 - Delegations

* Linter fixes

* Tests
  • Loading branch information
mlencki authored Sep 11, 2023
1 parent 85e2a9a commit 5c80f2b
Show file tree
Hide file tree
Showing 25 changed files with 789 additions and 44 deletions.
6 changes: 5 additions & 1 deletion app/Domain/Actions/VacationRequest/CreateAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ protected function createVacationRequest(array $data, User $creator): VacationRe

$vacationRequest->save();

$days = $this->workDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to);
$days = $this->workDaysCalculator->calculateDays(
$vacationRequest->from,
$vacationRequest->to,
$vacationRequest->type,
);

foreach ($days as $day) {
$vacationRequest->vacations()->create([
Expand Down
1 change: 1 addition & 0 deletions app/Domain/Enums/VacationType.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum VacationType: string
case Sick = "sick_vacation";
case Absence = "absence";
case RemoteWork = "remote_work";
case Delegation = "delegation";

public function label(): string
{
Expand Down
40 changes: 40 additions & 0 deletions app/Domain/UnavailableDaysRetriever.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Toby\Domain;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\YearPeriod;

class UnavailableDaysRetriever
{
public function __construct(
protected UserVacationStatsRetriever $vacationStatsRetriever,
protected VacationTypeConfigRetriever $configRetriever,
) {}

public function getUnavailableDays(User $user, YearPeriod $yearPeriod, ?VacationType $vacationType = null): Collection
{
$unavailableDays = $user->vacations()
->whereBelongsTo($yearPeriod)
->whereRelation(
"vacationRequest",
fn(Builder $query): Builder => $query->noStates(VacationRequestStatesRetriever::failedStates()),
)
->pluck("date");

if (!$vacationType || !$this->configRetriever->isDuringNonWorkDays($vacationType)) {
$unavailableDays->push(...$yearPeriod->weekends());
$unavailableDays->push(...$yearPeriod->holidays()->pluck("date"));
}

return $unavailableDays
->unique()
->sort()
->values();
}
}
1 change: 1 addition & 0 deletions app/Domain/UserVacationStatsRetriever.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public function getOtherApprovedVacationDays(User $user, YearPeriod $yearPeriod)
fn(Builder $query): Builder => $query
->whereIn("type", $this->getNotLimitableVacationTypes())
->whereNot("type", VacationType::RemoteWork)
->whereNot("type", VacationType::Delegation)
->states(VacationRequestStatesRetriever::successStates()),
)
->count();
Expand Down
13 changes: 13 additions & 0 deletions app/Domain/VacationTypeConfigRetriever.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Contracts\Config\Repository;
use Toby\Domain\Enums\EmploymentForm;
use Toby\Domain\Enums\Role;
use Toby\Domain\Enums\VacationType;

class VacationTypeConfigRetriever
Expand All @@ -16,6 +17,8 @@ class VacationTypeConfigRetriever
public const KEY_HAS_LIMIT = "has_limit";
public const KEY_AVAILABLE_FOR = "available_for";
public const KEY_IS_VACATION = "is_vacation";
public const KEY_DURING_NON_WORKDAYS = "during_non_workdays";
public const KEY_REQUEST_ALLOWED_FOR = "request_allowed_for";

public function __construct(
protected Repository $config,
Expand Down Expand Up @@ -46,11 +49,21 @@ public function isVacation(VacationType $type): bool
return $this->getConfigFor($type)[static::KEY_IS_VACATION];
}

public function isDuringNonWorkDays(VacationType $type): bool
{
return $this->getConfigFor($type)[static::KEY_DURING_NON_WORKDAYS];
}

public function isAvailableFor(VacationType $type, EmploymentForm $employmentForm): bool
{
return in_array($employmentForm, $this->getConfigFor($type)[static::KEY_AVAILABLE_FOR], true);
}

public function isRequestAllowedFor(VacationType $type, Role $role): bool
{
return in_array($role, $this->getConfigFor($type)[static::KEY_REQUEST_ALLOWED_FOR], true);
}

protected function getConfigFor(VacationType $type): array
{
return $this->config->get("vacation_types.{$type->value}");
Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function check(VacationRequest $vacationRequest): bool
$limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod);
$vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod);
$estimatedDays = $this->workDaysCalculator
->calculateDays($vacationRequest->from, $vacationRequest->to)
->calculateDays($vacationRequest->from, $vacationRequest->to, $vacationRequest->type)
->count();

return $limit >= ($vacationDays + $estimatedDays);
Expand Down
2 changes: 1 addition & 1 deletion app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public function __construct(
public function check(VacationRequest $vacationRequest): bool
{
return $this->workDaysCalculator
->calculateDays($vacationRequest->from, $vacationRequest->to)
->calculateDays($vacationRequest->from, $vacationRequest->to, $vacationRequest->type)
->isNotEmpty();
}

Expand Down
15 changes: 12 additions & 3 deletions app/Domain/WorkDaysCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
use Carbon\CarbonInterface;
use Carbon\CarbonPeriod;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\YearPeriod;

class WorkDaysCalculator
{
public function calculateDays(CarbonInterface $from, CarbonInterface $to): Collection
public function __construct(
protected VacationTypeConfigRetriever $configRetriever,
) {}

public function calculateDays(CarbonInterface $from, CarbonInterface $to, ?VacationType $vacationType = null): Collection
{
$period = CarbonPeriod::create($from, $to);
$yearPeriod = YearPeriod::findByYear($from->year);
Expand All @@ -20,16 +25,20 @@ public function calculateDays(CarbonInterface $from, CarbonInterface $to): Colle
$validDays = new Collection();

foreach ($period as $day) {
if ($this->passes($day, $holidays)) {
if ($this->passes($day, $holidays, $vacationType)) {
$validDays->add($day);
}
}

return $validDays;
}

protected function passes(CarbonInterface $day, Collection $holidays): bool
protected function passes(CarbonInterface $day, Collection $holidays, ?VacationType $vacationType = null): bool
{
if ($vacationType && $this->configRetriever->isDuringNonWorkDays($vacationType)) {
return true;
}

if ($day->isWeekend()) {
return false;
}
Expand Down
6 changes: 6 additions & 0 deletions app/Eloquent/Models/Vacation.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Toby\Eloquent\Models;

use Database\Factories\VacationFactory;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
Expand Down Expand Up @@ -68,4 +69,9 @@ public function scopeWhereTypes(Builder $query, Collection $types): Builder
fn(Builder $query): Builder => $query->whereIn("type", $types),
);
}

protected static function newFactory(): VacationFactory
{
return VacationFactory::new();
}
}
18 changes: 18 additions & 0 deletions app/Eloquent/Models/YearPeriod.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ public function holidays(): HasMany
return $this->hasMany(Holiday::class);
}

public function weekends(): Collection
{
$start = Carbon::create($this->year);
$end = Carbon::create($this->year)->endOfYear();

$weekends = new Collection();

while ($start->lessThanOrEqualTo($end)) {
if ($start->isWeekend()) {
$weekends->push($start->copy());
}

$start->addDay();
}

return $weekends;
}

protected static function newFactory(): YearPeriodFactory
{
return YearPeriodFactory::new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@

namespace Toby\Infrastructure\Http\Controllers\Api;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Carbon;
use Toby\Domain\UnavailableDaysRetriever;
use Toby\Domain\UserVacationStatsRetriever;
use Toby\Domain\VacationRequestStatesRetriever;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\User;
use Toby\Infrastructure\Http\Controllers\Controller;
Expand All @@ -20,23 +19,16 @@ public function __invoke(
CalculateUserUnavailableDaysRequest $request,
UserVacationStatsRetriever $vacationStatsRetriever,
YearPeriodRetriever $yearPeriodRetriever,
UnavailableDaysRetriever $unavailableDaysRetriever,
): JsonResponse {
/** @var User $user */
$user = User::query()->find($request->get("user"));
$yearPeriod = $yearPeriodRetriever->selected();

$holidays = $yearPeriod->holidays()->pluck("date");
$vacationDays = $user->vacations()
->whereBelongsTo($yearPeriod)
->whereRelation(
"vacationRequest",
fn(Builder $query): Builder => $query->noStates(VacationRequestStatesRetriever::failedStates()),
)
->pluck("date");
$unavailableDays = $unavailableDaysRetriever->getUnavailableDays($user, $yearPeriod, $request->vacationType())
->map(fn(Carbon $date): string => $date->toDateString())
->toArray();

return new JsonResponse([
...$holidays->map(fn(Carbon $date): string => $date->toDateString()),
...$vacationDays->map(fn(Carbon $date): string => $date->toDateString()),
]);
return new JsonResponse($unavailableDays);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CalculateVacationDaysController extends Controller
{
public function __invoke(CalculateVacationDaysRequest $request, WorkDaysCalculator $calculator): JsonResponse
{
$days = $calculator->calculateDays($request->from(), $request->to());
$days = $calculator->calculateDays($request->from(), $request->to(), $request->vacationType());

return new JsonResponse($days->map(fn(Carbon $day): string => $day->toDateString())->all());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function __invoke(

$types = VacationType::all()
->filter(fn(VacationType $type): bool => $configRetriever->isAvailableFor($type, $user->profile->employment_form))
->filter(fn(VacationType $type): bool => $configRetriever->isRequestAllowedFor($type, $request->user()->role))
->map(fn(VacationType $type): array => [
"label" => $type->label(),
"value" => $type->value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,23 @@
namespace Toby\Infrastructure\Http\Requests\Api;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use Toby\Domain\Enums\VacationType;

class CalculateUserUnavailableDaysRequest extends FormRequest
{
public function rules(): array
{
return [
"vacationType" => [new Enum(VacationType::class)],
"user" => ["required", "exists:users,id"],
];
}

public function vacationType(): ?VacationType
{
return $this->request->has("vacationType")
? VacationType::from($this->request->get("vacationType"))
: null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Carbon;
use Illuminate\Validation\Rules\Enum;
use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\YearPeriod;
use Toby\Infrastructure\Http\Rules\YearPeriodExists;

Expand All @@ -14,11 +16,17 @@ class CalculateVacationDaysRequest extends FormRequest
public function rules(): array
{
return [
"vacationType" => ["required", new Enum(VacationType::class)],
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
];
}

public function vacationType(): VacationType
{
return VacationType::from($this->request->get("vacationType"));
}

public function from(): Carbon
{
return Carbon::create($this->request->get("from"));
Expand Down
Loading

0 comments on commit 5c80f2b

Please sign in to comment.