#43 - vacation summary for employee (#66)

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* #5 - bump dependencies

* #43 - wip

* #43 - add composer script

* #43 - fix

* #43 - fix

* #43 - wip

* #43 - ecs fix

* #43 - cr fix

* #43 - cr fix

* #43 - fix

Co-authored-by: EwelinaLasowy <ewelina.lasowy@blumilk.pl>
This commit is contained in:
Adrian Hopek
2022-03-03 09:03:17 +01:00
committed by GitHub
parent d825dd727f
commit 3d9726039c
69 changed files with 3122 additions and 1762 deletions

View File

@@ -6,9 +6,6 @@ namespace Toby\Architecture\Providers;
use Illuminate\Support\Carbon;
use Illuminate\Support\ServiceProvider;
use Toby\Eloquent\Models\Holiday;
use Toby\Eloquent\Models\VacationLimit;
use Toby\Eloquent\Scopes\SelectedYearPeriodScope;
class AppServiceProvider extends ServiceProvider
{
@@ -16,10 +13,5 @@ class AppServiceProvider extends ServiceProvider
{
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
Carbon::macro("toDisplayDate", fn() => $this->translatedFormat("d.m.Y"));
$selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class);
VacationLimit::addGlobalScope($selectedYearPeriodScope);
Holiday::addGlobalScope($selectedYearPeriodScope);
}
}

View File

@@ -16,8 +16,7 @@ class CalendarGenerator
{
public function __construct(
protected YearPeriodRetriever $yearPeriodRetriever,
) {
}
) {}
public function generate(Carbon $month): array
{

View File

@@ -7,7 +7,7 @@ namespace Toby\Domain\Enums;
enum EmploymentForm: string
{
case EmploymentContract = "employment_contract";
case ComissionContract = "commission_contract";
case CommissionContract = "commission_contract";
case B2bContract = "b2b_contract";
case BoardMemberContract = "board_member_contract";

View File

@@ -15,6 +15,5 @@ class VacationRequestAcceptedByAdministrative
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestAcceptedByTechnical
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestApproved
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestCancelled
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestCreated
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestRejected
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -20,6 +20,5 @@ class VacationRequestStateChanged
public ?VacationRequestState $from,
public VacationRequestState $to,
public ?User $user = null,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestWaitsForAdminApproval
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -15,6 +15,5 @@ class VacationRequestWaitsForTechApproval
public function __construct(
public VacationRequest $vacationRequest,
) {
}
) {}
}

View File

@@ -11,8 +11,7 @@ class HandleAcceptedByAdministrativeVacationRequest
{
public function __construct(
protected VacationRequestStateManager $stateManager,
) {
}
) {}
public function handle(VacationRequestAcceptedByAdministrative $event): void
{

View File

@@ -13,8 +13,7 @@ class HandleAcceptedByTechnicalVacationRequest
public function __construct(
protected VacationTypeConfigRetriever $configRetriever,
protected VacationRequestStateManager $stateManager,
) {
}
) {}
public function handle(VacationRequestAcceptedByTechnical $event): void
{

View File

@@ -13,8 +13,7 @@ class HandleCreatedVacationRequest
public function __construct(
protected VacationTypeConfigRetriever $configRetriever,
protected VacationRequestStateManager $stateManager,
) {
}
) {}
public function handle(VacationRequestCreated $event): void
{

View File

@@ -13,8 +13,7 @@ use Toby\Eloquent\Models\User;
class SendApprovedVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestApproved $event): void
{

View File

@@ -13,8 +13,7 @@ use Toby\Eloquent\Models\User;
class SendCancelledVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestCancelled $event): void
{

View File

@@ -11,8 +11,7 @@ use Toby\Domain\Notifications\VacationRequestCreatedOnEmployeeBehalf;
class SendCreatedVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestCreated $event): void
{

View File

@@ -13,8 +13,7 @@ use Toby\Eloquent\Models\User;
class SendRejectedVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestRejected $event): void
{

View File

@@ -13,8 +13,7 @@ use Toby\Eloquent\Models\User;
class SendWaitedForAdministrativeVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestWaitsForAdminApproval $event): void
{

View File

@@ -13,8 +13,7 @@ use Toby\Eloquent\Models\User;
class SendWaitedForTechnicalVacationRequestNotification
{
public function __construct(
) {
}
) {}
public function handle(VacationRequestWaitsForTechApproval $event): void
{

View File

@@ -18,8 +18,7 @@ class VacationRequestApprovedNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {
}
) {}
public function via(): array
{

View File

@@ -18,8 +18,7 @@ class VacationRequestCancelledNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {
}
) {}
public function via(): array
{

View File

@@ -16,8 +16,7 @@ class VacationRequestCreatedNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
) {
}
) {}
public function via(): array
{

View File

@@ -16,8 +16,7 @@ class VacationRequestCreatedOnEmployeeBehalf extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
) {
}
) {}
public function via(): array
{

View File

@@ -18,8 +18,7 @@ class VacationRequestRejectedNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {
}
) {}
public function via(): array
{

View File

@@ -18,8 +18,7 @@ class VacationRequestWaitsForAdminApprovalNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {
}
) {}
public function via(): array
{

View File

@@ -18,8 +18,7 @@ class VacationRequestWaitsForTechApprovalNotification extends Notification
public function __construct(
protected VacationRequest $vacationRequest,
protected User $user,
) {
}
) {}
public function via(): array
{

View File

@@ -41,8 +41,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With
public function __construct(
protected User $user,
protected Carbon $month,
) {
}
) {}
public function title(): string
{

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Toby\Domain;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\YearPeriod;
class UserVacationStatsRetriever
{
public function __construct(
protected VacationTypeConfigRetriever $configRetriever,
) {}
public function getUsedVacationDays(User $user, YearPeriod $yearPeriod): int
{
return $user
->vacations()
->where("year_period_id", $yearPeriod->id)
->whereRelation(
"vacationRequest",
fn(Builder $query) => $query
->whereIn("type", $this->getLimitableVacationTypes())
->states(VacationRequestStatesRetriever::successStates()),
)
->count();
}
public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int
{
return $user
->vacations()
->where("year_period_id", $yearPeriod->id)
->whereRelation(
"vacationRequest",
fn(Builder $query) => $query
->whereIn("type", $this->getLimitableVacationTypes())
->states(VacationRequestStatesRetriever::pendingStates()),
)
->count();
}
public function getOtherApprovedVacationDays(User $user, YearPeriod $yearPeriod): int
{
return $user
->vacations()
->where("year_period_id", $yearPeriod->id)
->whereRelation(
"vacationRequest",
fn(Builder $query) => $query
->whereIn("type", $this->getNotLimitableVacationTypes())
->states(VacationRequestStatesRetriever::successStates()),
)
->count();
}
public function getRemainingVacationDays(User $user, YearPeriod $yearPeriod): int
{
$limit = $this->getVacationDaysLimit($user, $yearPeriod);
$used = $this->getUsedVacationDays($user, $yearPeriod);
$pending = $this->getPendingVacationDays($user, $yearPeriod);
return $limit - $used - $pending;
}
public function getVacationDaysLimit(User $user, YearPeriod $yearPeriod): int
{
$limit = $user->vacationLimits()
->where("year_period_id", $yearPeriod->id)
->first()
->days;
return $limit ?? 0;
}
protected function getLimitableVacationTypes(): Collection
{
$types = new Collection(VacationType::cases());
return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type));
}
protected function getNotLimitableVacationTypes(): Collection
{
$types = new Collection(VacationType::cases());
return $types->filter(fn(VacationType $type) => !$this->configRetriever->hasLimit($type));
}
}

View File

@@ -31,8 +31,7 @@ class VacationRequestStateManager
public function __construct(
protected Auth $auth,
protected Dispatcher $dispatcher,
) {
}
) {}
public function markAsCreated(VacationRequest $vacationRequest, ?User $user = null): void
{

View File

@@ -16,8 +16,7 @@ class VacationTypeConfigRetriever
public function __construct(
protected Repository $config,
) {
}
) {}
public function needsTechnicalApproval(VacationType $type): bool
{

View File

@@ -19,8 +19,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule
public function __construct(
protected VacationTypeConfigRetriever $configRetriever,
protected VacationDaysCalculator $vacationDaysCalculator,
) {
}
) {}
public function check(VacationRequest $vacationRequest): bool
{

View File

@@ -11,8 +11,7 @@ class MinimumOneVacationDayRule implements VacationRequestRule
{
public function __construct(
protected VacationDaysCalculator $vacationDaysCalculator,
) {
}
) {}
public function check(VacationRequest $vacationRequest): bool
{

View File

@@ -26,8 +26,7 @@ class VacationRequestValidator
public function __construct(
protected Container $container,
) {
}
) {}
/**
* @throws ValidationException

View File

@@ -14,8 +14,7 @@ class UserAvatarGenerator
{
public function __construct(
protected InitialAvatar $generator,
) {
}
) {}
public function generateFor(User $user): string
{

View File

@@ -13,8 +13,7 @@ class YearPeriodRetriever
public function __construct(
protected Session $session,
) {
}
) {}
public function selected(): YearPeriod
{

View File

@@ -4,6 +4,8 @@ declare(strict_types=1);
namespace Toby\Eloquent\Models;
use Database\Factories\VacationRequestActivityFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Toby\Domain\States\VacationRequest\VacationRequestState;
@@ -17,6 +19,8 @@ use Toby\Domain\States\VacationRequest\VacationRequestState;
*/
class VacationRequestActivity extends Model
{
use HasFactory;
protected $guarded = [];
protected $casts = [
@@ -33,4 +37,9 @@ class VacationRequestActivity extends Model
{
return $this->belongsTo(VacationRequest::class);
}
protected static function newFactory(): VacationRequestActivityFactory
{
return VacationRequestActivityFactory::new();
}
}

View File

@@ -14,8 +14,7 @@ class UserObserver
public function __construct(
protected UserAvatarGenerator $generator,
protected YearPeriodRetriever $yearPeriodRetriever,
) {
}
) {}
public function created(User $user): void
{

View File

@@ -13,8 +13,7 @@ class VacationRequestObserver
public function __construct(
protected Auth $auth,
protected Dispatcher $dispatcher,
) {
}
) {}
public function creating(VacationRequest $vacationRequest): void
{

View File

@@ -14,8 +14,7 @@ class YearPeriodObserver
public function __construct(
protected UserAvatarGenerator $generator,
protected PolishHolidaysRetriever $polishHolidaysRetriever,
) {
}
) {}
public function created(YearPeriod $yearPeriod): void
{

View File

@@ -13,8 +13,7 @@ class SelectedYearPeriodScope implements Scope
{
public function __construct(
protected YearPeriodRetriever $yearPeriodRetriever,
) {
}
) {}
public function apply(Builder $builder, Model $model): Builder
{

View File

@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Http\Controllers\Api;
use Illuminate\Http\JsonResponse;
use Toby\Domain\UserVacationStatsRetriever;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\User;
use Toby\Infrastructure\Http\Controllers\Controller;
use Toby\Infrastructure\Http\Requests\Api\CalculateVacationStatsRequest;
class CalculateUserVacationStatsController extends Controller
{
public function __invoke(
CalculateVacationStatsRequest $request,
UserVacationStatsRetriever $vacationStatsRetriever,
YearPeriodRetriever $yearPeriodRetriever,
): JsonResponse {
/** @var User $user */
$user = User::query()->find($request->get("user"));
$yearPeriod = $yearPeriodRetriever->selected();
$limit = $vacationStatsRetriever->getVacationDaysLimit($user, $yearPeriod);
$used = $vacationStatsRetriever->getUsedVacationDays($user, $yearPeriod);
$pending = $vacationStatsRetriever->getPendingVacationDays($user, $yearPeriod);
$other = $vacationStatsRetriever->getOtherApprovedVacationDays($user, $yearPeriod);
$remaining = $limit - $used - $pending;
return new JsonResponse([
"limit" => $limit,
"remaining" => $remaining,
"used" => $used,
"pending" => $pending,
"other" => $other,
]);
}
}

View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Http\Controllers;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Inertia\Response;
use Toby\Domain\UserVacationStatsRetriever;
use Toby\Domain\VacationRequestStatesRetriever;
use Toby\Eloquent\Models\Holiday;
use Toby\Eloquent\Models\Vacation;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod;
use Toby\Infrastructure\Http\Resources\AbsenceResource;
use Toby\Infrastructure\Http\Resources\HolidayResource;
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
class DashboardController extends Controller
{
public function __invoke(Request $request, UserVacationStatsRetriever $vacationStatsRetriever): Response
{
$user = $request->user();
$now = Carbon::now();
$yearPeriod = YearPeriod::findByYear($now->year);
$absences = Vacation::query()
->with(["user", "vacationRequest"])
->whereDate("date", $now)
->whereRelation(
"vacationRequest",
fn(Builder $query) => $query->states(VacationRequestStatesRetriever::successStates()),
)
->get();
$vacationRequests = VacationRequest::query()
->latest("updated_at")
->limit(3)
->get();
$holidays = Holiday::query()
->whereDate("date", ">=", $now)
->latest()
->limit(3)
->get();
$limit = $vacationStatsRetriever->getVacationDaysLimit($user, $yearPeriod);
$used = $vacationStatsRetriever->getUsedVacationDays($user, $yearPeriod);
$pending = $vacationStatsRetriever->getPendingVacationDays($user, $yearPeriod);
$other = $vacationStatsRetriever->getOtherApprovedVacationDays($user, $yearPeriod);
$remaining = $limit - $used - $pending;
return inertia("Dashboard", [
"absences" => AbsenceResource::collection($absences),
"vacationRequests" => VacationRequestResource::collection($vacationRequests),
"holidays" => HolidayResource::collection($holidays),
"stats" => [
"limit" => $limit,
"remaining" => $remaining,
"used" => $used,
"pending" => $pending,
"other" => $other,
],
]);
}
}

View File

@@ -8,6 +8,7 @@ use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Inertia\Response;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\Holiday;
use Toby\Infrastructure\Http\Requests\HolidayRequest;
use Toby\Infrastructure\Http\Resources\HolidayFormDataResource;
@@ -15,9 +16,12 @@ use Toby\Infrastructure\Http\Resources\HolidayResource;
class HolidayController extends Controller
{
public function index(Request $request): Response
public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
{
$holidays = Holiday::query()
$yearPeriod = $yearPeriodRetriever->selected();
$holidays = $yearPeriod
->holidays()
->orderBy("date")
->get();

View File

@@ -6,24 +6,41 @@ namespace Toby\Infrastructure\Http\Controllers;
use Illuminate\Http\RedirectResponse;
use Inertia\Response;
use Toby\Domain\UserVacationStatsRetriever;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\VacationLimit;
use Toby\Eloquent\Models\YearPeriod;
use Toby\Infrastructure\Http\Requests\VacationLimitRequest;
use Toby\Infrastructure\Http\Resources\VacationLimitResource;
use Toby\Infrastructure\Http\Resources\UserResource;
class VacationLimitController extends Controller
{
public function edit(): Response
public function edit(YearPeriodRetriever $yearPeriodRetriever, UserVacationStatsRetriever $statsRetriever): Response
{
$this->authorize("manageVacationLimits");
$limits = VacationLimit::query()
$yearPeriod = $yearPeriodRetriever->selected();
$previousYearPeriod = YearPeriod::findByYear($yearPeriod->year - 1);
$limits = $yearPeriod
->vacationLimits()
->with("user")
->orderByUserField("last_name")
->orderByUserField("first_name")
->get();
$limitsResource = $limits->map(fn(VacationLimit $limit) => [
"id" => $limit->id,
"user" => new UserResource($limit->user),
"hasVacation" => $limit->hasVacation(),
"days" => $limit->days,
"remainingLastYear" => $previousYearPeriod
? $statsRetriever->getRemainingVacationDays($limit->user, $previousYearPeriod)
: 0,
]);
return inertia("VacationLimits", [
"limits" => VacationLimitResource::collection($limits),
"limits" => $limitsResource,
]);
}

View File

@@ -6,6 +6,7 @@ namespace Toby\Infrastructure\Http\Controllers;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
@@ -88,9 +89,15 @@ class VacationRequestController extends Controller
return $pdf->stream();
}
public function create(Request $request): Response
public function create(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
{
$yearPeriod = $yearPeriodRetriever->selected();
$users = User::query()
->whereRelation(
"vacationlimits",
fn(Builder $query) => $query->where("year_period_id", $yearPeriod->id)->whereNotNull("days"),
)
->orderBy("last_name")
->orderBy("first_name")
->get();

View File

@@ -13,8 +13,7 @@ class HandleInertiaRequests extends Middleware
{
public function __construct(
protected YearPeriodRetriever $yearPeriodRetriever,
) {
}
) {}
public function share(Request $request): array
{

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
class CalculateVacationStatsRequest extends FormRequest
{
public function rules(): array
{
return [
"user" => ["required", "exists:users,id"],
];
}
}

View File

@@ -6,7 +6,7 @@ namespace Toby\Infrastructure\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class VacationLimitResource extends JsonResource
class AbsenceResource extends JsonResource
{
public static $wrap = null;
@@ -15,8 +15,7 @@ class VacationLimitResource extends JsonResource
return [
"id" => $this->id,
"user" => new UserResource($this->user),
"hasVacation" => $this->hasVacation(),
"days" => $this->days,
"date" => $this->date->toDisplayString(),
];
}
}

View File

@@ -18,8 +18,7 @@ class ClearVacationRequestDaysInGoogleCalendar implements ShouldQueue
public function __construct(
protected VacationRequest $vacationRequest,
) {
}
) {}
public function handle(): void
{

View File

@@ -18,8 +18,7 @@ class SendVacationRequestDaysToGoogleCalendar implements ShouldQueue
public function __construct(
protected VacationRequest $vacationRequest,
) {
}
) {}
public function handle(): void
{