Merge branch 'main' into states
# Conflicts: # app/Domain/CalendarGenerator.php # app/Domain/VacationRequestStateManager.php # app/Eloquent/Models/VacationRequest.php # app/Eloquent/Observers/VacationRequestObserver.php # composer.json # composer.lock # tests/Feature/VacationRequestTest.php
This commit is contained in:
commit
61f7bbeca7
@ -1,4 +1,4 @@
|
|||||||
APP_NAME="Toby HR applicaiton"
|
APP_NAME="Toby HR application"
|
||||||
APP_ENV=local
|
APP_ENV=local
|
||||||
APP_KEY=
|
APP_KEY=
|
||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
|
@ -15,6 +15,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
|
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
|
||||||
|
Carbon::macro("toDisplayDate", fn() => $this->translatedFormat("d.m.Y"));
|
||||||
|
|
||||||
$selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class);
|
$selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class);
|
||||||
|
|
||||||
|
@ -10,22 +10,34 @@ use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
|||||||
use Toby\Domain\Events\VacationRequestApproved;
|
use Toby\Domain\Events\VacationRequestApproved;
|
||||||
use Toby\Domain\Events\VacationRequestCancelled;
|
use Toby\Domain\Events\VacationRequestCancelled;
|
||||||
use Toby\Domain\Events\VacationRequestCreated;
|
use Toby\Domain\Events\VacationRequestCreated;
|
||||||
|
use Toby\Domain\Events\VacationRequestRejected;
|
||||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||||
|
use Toby\Domain\Events\VacationRequestWaitsForAdminApproval;
|
||||||
|
use Toby\Domain\Events\VacationRequestWaitsForTechApproval;
|
||||||
use Toby\Domain\Listeners\CreateVacationRequestActivity;
|
use Toby\Domain\Listeners\CreateVacationRequestActivity;
|
||||||
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
|
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
|
||||||
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
|
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
|
||||||
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
|
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
|
||||||
use Toby\Domain\Listeners\HandleCancelledVacationRequest;
|
use Toby\Domain\Listeners\HandleCancelledVacationRequest;
|
||||||
use Toby\Domain\Listeners\HandleCreatedVacationRequest;
|
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
|
class EventServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
protected $listen = [
|
protected $listen = [
|
||||||
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
|
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
|
||||||
VacationRequestCreated::class => [HandleCreatedVacationRequest::class],
|
VacationRequestCreated::class => [HandleCreatedVacationRequest::class, SendCreatedVacationRequestNotification::class],
|
||||||
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
|
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
|
||||||
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
|
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
|
||||||
VacationRequestApproved::class => [HandleApprovedVacationRequest::class],
|
VacationRequestApproved::class => [HandleApprovedVacationRequest::class, SendApprovedVacationRequestNotification::class],
|
||||||
VacationRequestCancelled::class => [HandleCancelledVacationRequest::class],
|
VacationRequestRejected::class => [SendRejectedVacationRequestNotification::class],
|
||||||
|
VacationRequestCancelled::class => [HandleCancelledVacationRequest::class, SendCancelledVacationRequestNotification::class],
|
||||||
|
VacationRequestWaitsForTechApproval::class => [SendWaitedForTechnicalVacationRequestNotification::class],
|
||||||
|
VacationRequestWaitsForAdminApproval::class => [SendWaitedForAdministrativeVacationRequestNotification::class],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Toby\Domain;
|
namespace Toby\Domain;
|
||||||
|
|
||||||
use Carbon\CarbonImmutable;
|
|
||||||
use Carbon\CarbonInterface;
|
|
||||||
use Carbon\CarbonPeriod;
|
use Carbon\CarbonPeriod;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
use Toby\Eloquent\Models\Vacation;
|
use Toby\Eloquent\Models\Vacation;
|
||||||
@ -20,33 +19,16 @@ class CalendarGenerator
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generate(YearPeriod $yearPeriod, string $month): array
|
public function generate(Carbon $month): array
|
||||||
{
|
{
|
||||||
$date = CarbonImmutable::create($yearPeriod->year, $this->monthNameToNumber($month));
|
$period = CarbonPeriod::create($month->copy()->startOfMonth(), $month->copy()->endOfMonth());
|
||||||
$period = CarbonPeriod::create($date->startOfMonth(), $date->endOfMonth());
|
$yearPeriod = YearPeriod::findByYear($month->year);
|
||||||
|
|
||||||
$holidays = $yearPeriod->holidays()->pluck("date");
|
$holidays = $yearPeriod->holidays()->pluck("date");
|
||||||
|
|
||||||
return $this->generateCalendar($period, $holidays);
|
return $this->generateCalendar($period, $holidays);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function monthNameToNumber($name): int
|
|
||||||
{
|
|
||||||
return match ($name) {
|
|
||||||
default => CarbonInterface::JANUARY,
|
|
||||||
"february" => CarbonInterface::FEBRUARY,
|
|
||||||
"march" => CarbonInterface::MARCH,
|
|
||||||
"april" => CarbonInterface::APRIL,
|
|
||||||
"may" => CarbonInterface::MAY,
|
|
||||||
"june" => CarbonInterface::JUNE,
|
|
||||||
"july" => CarbonInterface::JULY,
|
|
||||||
"august" => CarbonInterface::AUGUST,
|
|
||||||
"september" => CarbonInterface::SEPTEMBER,
|
|
||||||
"october" => CarbonInterface::OCTOBER,
|
|
||||||
"november" => CarbonInterface::NOVEMBER,
|
|
||||||
"december" => CarbonInterface::DECEMBER,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function generateCalendar(CarbonPeriod $period, Collection $holidays): array
|
protected function generateCalendar(CarbonPeriod $period, Collection $holidays): array
|
||||||
{
|
{
|
||||||
$calendar = [];
|
$calendar = [];
|
||||||
|
53
app/Domain/Enums/Month.php
Normal file
53
app/Domain/Enums/Month.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Enums;
|
||||||
|
|
||||||
|
use Carbon\CarbonInterface;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
enum Month: string
|
||||||
|
{
|
||||||
|
case January = "january";
|
||||||
|
case February = "february";
|
||||||
|
case March = "march";
|
||||||
|
case April = "april";
|
||||||
|
case May = "may";
|
||||||
|
case June = "june";
|
||||||
|
case July = "july";
|
||||||
|
case August = "august";
|
||||||
|
case September = "september";
|
||||||
|
case October = "october";
|
||||||
|
case November = "november";
|
||||||
|
case December = "december";
|
||||||
|
|
||||||
|
public function toCarbonNumber(): int
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::January => CarbonInterface::JANUARY,
|
||||||
|
self::February => CarbonInterface::FEBRUARY,
|
||||||
|
self::March => CarbonInterface::MARCH,
|
||||||
|
self::April => CarbonInterface::APRIL,
|
||||||
|
self::May => CarbonInterface::MAY,
|
||||||
|
self::June => CarbonInterface::JUNE,
|
||||||
|
self::July => CarbonInterface::JULY,
|
||||||
|
self::August => CarbonInterface::AUGUST,
|
||||||
|
self::September => CarbonInterface::SEPTEMBER,
|
||||||
|
self::October => CarbonInterface::OCTOBER,
|
||||||
|
self::November => CarbonInterface::NOVEMBER,
|
||||||
|
self::December => CarbonInterface::DECEMBER,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function current(): Month
|
||||||
|
{
|
||||||
|
return Month::from(Str::lower(Carbon::now()->englishMonth));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function fromNameOrCurrent(string $name): Month
|
||||||
|
{
|
||||||
|
return Month::tryFrom($name) ?? Month::current();
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestRejected.php
Normal file
20
app/Domain/Events/VacationRequestRejected.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?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,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestWaitsForAdminApproval.php
Normal file
20
app/Domain/Events/VacationRequestWaitsForAdminApproval.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?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,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestWaitsForTechApproval.php
Normal file
20
app/Domain/Events/VacationRequestWaitsForTechApproval.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?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,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,12 @@ class HandleCreatedVacationRequest
|
|||||||
{
|
{
|
||||||
$vacationRequest = $event->vacationRequest;
|
$vacationRequest = $event->vacationRequest;
|
||||||
|
|
||||||
|
if ($vacationRequest->hasFlowSkipped()) {
|
||||||
|
$this->stateManager->approve($vacationRequest);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
|
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
|
||||||
$this->stateManager->waitForTechnical($vacationRequest);
|
$this->stateManager->waitForTechnical($vacationRequest);
|
||||||
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<?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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,34 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
<?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();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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 VacationRequestApprovedNotification 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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$days = $this->vacationRequest->vacations()->count();
|
||||||
|
$requester = $this->vacationRequest->user->fullName;
|
||||||
|
|
||||||
|
return (new MailMessage())
|
||||||
|
->greeting(__("Hi :user!", [
|
||||||
|
"user" => $user,
|
||||||
|
]))
|
||||||
|
->subject(__("Vacation request :title has been approved", [
|
||||||
|
"title" => $title,
|
||||||
|
]))
|
||||||
|
->line(__("The vacation request :title for user :requester has been approved.", [
|
||||||
|
"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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<?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 VacationRequestCreatedNotification 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
|
||||||
|
{
|
||||||
|
$user = $this->vacationRequest->user->first_name;
|
||||||
|
$title = $this->vacationRequest->name;
|
||||||
|
$type = $this->vacationRequest->type->label();
|
||||||
|
$from = $this->vacationRequest->from->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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,
|
||||||
|
]))
|
||||||
|
->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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
<?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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
<?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 VacationRequestWaitsForTechApprovalNotification 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->toDisplayDate();
|
||||||
|
$to = $this->vacationRequest->to->toDisplayDate();
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
37
app/Domain/TimesheetExport.php
Normal file
37
app/Domain/TimesheetExport.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
|
||||||
|
class TimesheetExport implements WithMultipleSheets
|
||||||
|
{
|
||||||
|
protected Collection $users;
|
||||||
|
protected Carbon $month;
|
||||||
|
|
||||||
|
public function sheets(): array
|
||||||
|
{
|
||||||
|
return $this->users
|
||||||
|
->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month))
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forUsers(Collection $users): static
|
||||||
|
{
|
||||||
|
$this->users = $users;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forMonth(Carbon $month): static
|
||||||
|
{
|
||||||
|
$this->month = $month;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
222
app/Domain/TimesheetPerUserSheet.php
Normal file
222
app/Domain/TimesheetPerUserSheet.php
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain;
|
||||||
|
|
||||||
|
use Carbon\CarbonInterface;
|
||||||
|
use Carbon\CarbonPeriod;
|
||||||
|
use Generator;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromGenerator;
|
||||||
|
use Maatwebsite\Excel\Concerns\RegistersEventListeners;
|
||||||
|
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithStyles;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||||
|
use Maatwebsite\Excel\Events\AfterSheet;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Shared\Date;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Alignment;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Border;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\Fill;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
|
||||||
|
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Eloquent\Models\Holiday;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\Vacation;
|
||||||
|
|
||||||
|
class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, WithStyles, WithStrictNullComparison, ShouldAutoSize, FromGenerator
|
||||||
|
{
|
||||||
|
use RegistersEventListeners;
|
||||||
|
|
||||||
|
protected const HOURS_PER_DAY = 8;
|
||||||
|
protected const START_HOUR = 8;
|
||||||
|
protected const END_HOUR = 16;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected User $user,
|
||||||
|
protected Carbon $month,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function title(): string
|
||||||
|
{
|
||||||
|
return $this->user->fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
$types = VacationType::cases();
|
||||||
|
|
||||||
|
$headings = [
|
||||||
|
__("Date"),
|
||||||
|
__("Day of week"),
|
||||||
|
__("Start date"),
|
||||||
|
__("End date"),
|
||||||
|
__("Worked hours"),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($types as $type) {
|
||||||
|
$headings[] = $type->label();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generator(): Generator
|
||||||
|
{
|
||||||
|
$period = CarbonPeriod::create($this->month->copy()->startOfMonth(), $this->month->copy()->endOfMonth());
|
||||||
|
$vacations = $this->getVacationsForPeriod($this->user, $period);
|
||||||
|
$holidays = $this->getHolidaysForPeriod($period);
|
||||||
|
|
||||||
|
foreach ($period as $day) {
|
||||||
|
$vacationsForDay = $vacations->get($day->toDateString(), new Collection());
|
||||||
|
$workedThisDay = $this->checkIfWorkedThisDay($day, $holidays, $vacationsForDay);
|
||||||
|
|
||||||
|
$row = [
|
||||||
|
Date::dateTimeToExcel($day),
|
||||||
|
$day->translatedFormat("l"),
|
||||||
|
$workedThisDay ? $this->toExcelTime(Carbon::createFromTime(static::START_HOUR)) : null,
|
||||||
|
$workedThisDay ? $this->toExcelTime(Carbon::createFromTime(static::END_HOUR)) : null,
|
||||||
|
$workedThisDay ? static::HOURS_PER_DAY : null,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (VacationType::cases() as $type) {
|
||||||
|
$row[] = $vacationsForDay->has($type->value) ? static::HOURS_PER_DAY : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function styles(Worksheet $sheet): void
|
||||||
|
{
|
||||||
|
$lastRow = $sheet->getHighestRow();
|
||||||
|
$lastColumn = $sheet->getHighestColumn();
|
||||||
|
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}1")
|
||||||
|
->getFont()->setBold(true);
|
||||||
|
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}1")
|
||||||
|
->getAlignment()
|
||||||
|
->setVertical(Alignment::VERTICAL_CENTER);
|
||||||
|
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}1")
|
||||||
|
->getFill()
|
||||||
|
->setFillType(Fill::FILL_SOLID)
|
||||||
|
->getStartColor()
|
||||||
|
->setRGB("D9D9D9");
|
||||||
|
|
||||||
|
$sheet->getStyle("C1:{$lastColumn}{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
$sheet->getStyle("A2:A{$lastRow}")
|
||||||
|
->getNumberFormat()
|
||||||
|
->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
|
||||||
|
|
||||||
|
$sheet->getStyle("C1:D{$lastRow}")
|
||||||
|
->getNumberFormat()
|
||||||
|
->setFormatCode(NumberFormat::FORMAT_DATE_TIME3);
|
||||||
|
|
||||||
|
$sheet->getStyle("A2:A{$lastRow}")
|
||||||
|
->getFont()
|
||||||
|
->setBold(true);
|
||||||
|
|
||||||
|
for ($i = 2; $i < $lastRow; $i++) {
|
||||||
|
$date = Date::excelToDateTimeObject($sheet->getCell("A{$i}")->getValue());
|
||||||
|
|
||||||
|
if (Carbon::createFromInterface($date)->isWeekend()) {
|
||||||
|
$sheet->getStyle("A{$i}:{$lastColumn}{$i}")
|
||||||
|
->getFill()
|
||||||
|
->setFillType(Fill::FILL_SOLID)
|
||||||
|
->getStartColor()
|
||||||
|
->setRGB("FEE2E2");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sheet->getStyle("A1:{$lastColumn}{$lastRow}")
|
||||||
|
->getBorders()
|
||||||
|
->getAllBorders()
|
||||||
|
->setBorderStyle(Border::BORDER_THIN)
|
||||||
|
->getColor()
|
||||||
|
->setRGB("B7B7B7");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function afterSheet(AfterSheet $event): void
|
||||||
|
{
|
||||||
|
$sheet = $event->getSheet();
|
||||||
|
$lastRow = $sheet->getDelegate()->getHighestRow();
|
||||||
|
|
||||||
|
$sheet->append([
|
||||||
|
__("Sum:"),
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"=SUM(E2:E{$lastRow})",
|
||||||
|
]);
|
||||||
|
|
||||||
|
$lastRow++;
|
||||||
|
|
||||||
|
$sheet->getDelegate()->getStyle("A{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_RIGHT);
|
||||||
|
|
||||||
|
$sheet->getDelegate()->getStyle("A{$lastRow}")
|
||||||
|
->getFont()
|
||||||
|
->setBold(true);
|
||||||
|
|
||||||
|
$sheet->getDelegate()->getStyle("E{$lastRow}")
|
||||||
|
->getAlignment()
|
||||||
|
->setHorizontal(Alignment::HORIZONTAL_CENTER);
|
||||||
|
|
||||||
|
$sheet->getDelegate()->mergeCells("A{$lastRow}:D{$lastRow}");
|
||||||
|
|
||||||
|
$sheet->getDelegate()->getStyle("A{$lastRow}:E{$lastRow}")
|
||||||
|
->getBorders()
|
||||||
|
->getAllBorders()
|
||||||
|
->setBorderStyle(Border::BORDER_THIN)
|
||||||
|
->getColor()
|
||||||
|
->setRGB("B7B7B7");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getVacationsForPeriod(User $user, CarbonPeriod $period): Collection
|
||||||
|
{
|
||||||
|
return $user->vacations()
|
||||||
|
->with("vacationRequest")
|
||||||
|
->whereBetween("date", [$period->start, $period->end])
|
||||||
|
->whereRelation("vacationRequest", "state", VacationRequestState::Approved->value)
|
||||||
|
->get()
|
||||||
|
->groupBy(
|
||||||
|
[
|
||||||
|
fn(Vacation $vacation) => $vacation->date->toDateString(),
|
||||||
|
fn(Vacation $vacation) => $vacation->vacationRequest->type->value,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getHolidaysForPeriod(CarbonPeriod $period): Collection
|
||||||
|
{
|
||||||
|
return Holiday::query()
|
||||||
|
->whereBetween("date", [$period->start, $period->end])
|
||||||
|
->pluck("date");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function toExcelTime(Carbon $time): float
|
||||||
|
{
|
||||||
|
$excelTimestamp = Date::dateTimeToExcel($time);
|
||||||
|
$excelDate = floor($excelTimestamp);
|
||||||
|
|
||||||
|
return $excelTimestamp - $excelDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkIfWorkedThisDay(CarbonInterface $day, Collection $holidays, Collection $vacations): bool
|
||||||
|
{
|
||||||
|
return $day->isWeekday() && $holidays->doesntContain($day) && $vacations->isEmpty();
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,10 @@ use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
|||||||
use Toby\Domain\Events\VacationRequestApproved;
|
use Toby\Domain\Events\VacationRequestApproved;
|
||||||
use Toby\Domain\Events\VacationRequestCancelled;
|
use Toby\Domain\Events\VacationRequestCancelled;
|
||||||
use Toby\Domain\Events\VacationRequestCreated;
|
use Toby\Domain\Events\VacationRequestCreated;
|
||||||
|
use Toby\Domain\Events\VacationRequestRejected;
|
||||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
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\AcceptedByAdministrative;
|
||||||
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
|
use Toby\Domain\States\VacationRequest\AcceptedByTechnical;
|
||||||
use Toby\Domain\States\VacationRequest\Approved;
|
use Toby\Domain\States\VacationRequest\Approved;
|
||||||
@ -48,6 +51,7 @@ class VacationRequestStateManager
|
|||||||
public function reject(VacationRequest $vacationRequest, ?User $user = null): void
|
public function reject(VacationRequest $vacationRequest, ?User $user = null): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, Rejected::class, $user);
|
$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 = null): void
|
||||||
@ -74,11 +78,15 @@ class VacationRequestStateManager
|
|||||||
public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void
|
public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, WaitingForTechnical::class, $user);
|
$this->changeState($vacationRequest, WaitingForTechnical::class, $user);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
|
public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
|
||||||
{
|
{
|
||||||
$this->changeState($vacationRequest, WaitingForAdministrative::class, $user);
|
$this->changeState($vacationRequest, WaitingForAdministrative::class, $user);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
|
protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
|
||||||
|
@ -58,6 +58,11 @@ class User extends Authenticatable
|
|||||||
return $this->hasMany(VacationRequest::class);
|
return $this->hasMany(VacationRequest::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function createdVacationRequests(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(VacationRequest::class, "creator_id");
|
||||||
|
}
|
||||||
|
|
||||||
public function vacations(): HasMany
|
public function vacations(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(Vacation::class);
|
return $this->hasMany(Vacation::class);
|
||||||
|
@ -23,7 +23,9 @@ use Toby\Domain\States\VacationRequest\VacationRequestState;
|
|||||||
* @property Carbon $from
|
* @property Carbon $from
|
||||||
* @property Carbon $to
|
* @property Carbon $to
|
||||||
* @property string $comment
|
* @property string $comment
|
||||||
|
* @property bool $flow_skipped
|
||||||
* @property User $user
|
* @property User $user
|
||||||
|
* @property User $creator
|
||||||
* @property YearPeriod $yearPeriod
|
* @property YearPeriod $yearPeriod
|
||||||
* @property Collection $activities
|
* @property Collection $activities
|
||||||
* @property Collection $vacations
|
* @property Collection $vacations
|
||||||
@ -49,6 +51,11 @@ class VacationRequest extends Model
|
|||||||
return $this->belongsTo(User::class);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function creator(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, "creator_id");
|
||||||
|
}
|
||||||
|
|
||||||
public function yearPeriod(): BelongsTo
|
public function yearPeriod(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(YearPeriod::class);
|
return $this->belongsTo(YearPeriod::class);
|
||||||
@ -80,6 +87,11 @@ class VacationRequest extends Model
|
|||||||
->where("to", ">=", $vacationRequest->from);
|
->where("to", ">=", $vacationRequest->from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasFlowSkipped(): bool
|
||||||
|
{
|
||||||
|
return $this->flow_skipped;
|
||||||
|
}
|
||||||
|
|
||||||
protected static function newFactory(): VacationRequestFactory
|
protected static function newFactory(): VacationRequestFactory
|
||||||
{
|
{
|
||||||
return VacationRequestFactory::new();
|
return VacationRequestFactory::new();
|
||||||
|
@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Toby\Eloquent\Observers;
|
namespace Toby\Eloquent\Observers;
|
||||||
|
|
||||||
use Illuminate\Contracts\Auth\Factory as Auth;
|
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||||
use Illuminate\Events\Dispatcher;
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
use Toby\Eloquent\Models\VacationRequest;
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
class VacationRequestObserver
|
class VacationRequestObserver
|
||||||
|
35
app/Infrastructure/Http/Controllers/TimesheetController.php
Normal file
35
app/Infrastructure/Http/Controllers/TimesheetController.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
||||||
|
use Toby\Domain\Enums\Month;
|
||||||
|
use Toby\Domain\TimesheetExport;
|
||||||
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
|
||||||
|
class TimesheetController extends Controller
|
||||||
|
{
|
||||||
|
public function __invoke(Month $month, YearPeriodRetriever $yearPeriodRetriever): BinaryFileResponse
|
||||||
|
{
|
||||||
|
$yearPeriod = $yearPeriodRetriever->selected();
|
||||||
|
$carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber());
|
||||||
|
|
||||||
|
$users = User::query()
|
||||||
|
->orderBy("last_name")
|
||||||
|
->orderBy("first_name")
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$filename = "{$carbonMonth->translatedFormat("F Y")}.xlsx";
|
||||||
|
|
||||||
|
$timesheet = (new TimesheetExport())
|
||||||
|
->forMonth($carbonMonth)
|
||||||
|
->forUsers($users);
|
||||||
|
|
||||||
|
return Excel::download($timesheet, $filename);
|
||||||
|
}
|
||||||
|
}
|
@ -4,11 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Toby\Infrastructure\Http\Controllers;
|
namespace Toby\Infrastructure\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
use Toby\Domain\CalendarGenerator;
|
use Toby\Domain\CalendarGenerator;
|
||||||
|
use Toby\Domain\Enums\Month;
|
||||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
use Toby\Infrastructure\Http\Resources\UserResource;
|
use Toby\Infrastructure\Http\Resources\UserResource;
|
||||||
@ -16,22 +15,25 @@ use Toby\Infrastructure\Http\Resources\UserResource;
|
|||||||
class VacationCalendarController extends Controller
|
class VacationCalendarController extends Controller
|
||||||
{
|
{
|
||||||
public function index(
|
public function index(
|
||||||
Request $request,
|
|
||||||
YearPeriodRetriever $yearPeriodRetriever,
|
YearPeriodRetriever $yearPeriodRetriever,
|
||||||
CalendarGenerator $calendarGenerator,
|
CalendarGenerator $calendarGenerator,
|
||||||
|
?string $month = null,
|
||||||
): Response {
|
): Response {
|
||||||
$month = Str::lower($request->query("month", Carbon::now()->englishMonth));
|
$month = Month::fromNameOrCurrent((string)$month);
|
||||||
|
|
||||||
$yearPeriod = $yearPeriodRetriever->selected();
|
$yearPeriod = $yearPeriodRetriever->selected();
|
||||||
|
$carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber());
|
||||||
|
|
||||||
$users = User::query()
|
$users = User::query()
|
||||||
->orderBy("last_name")
|
->orderBy("last_name")
|
||||||
->orderBy("first_name")
|
->orderBy("first_name")
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$calendar = $calendarGenerator->generate($yearPeriod, $month);
|
$calendar = $calendarGenerator->generate($carbonMonth);
|
||||||
|
|
||||||
return inertia("Calendar", [
|
return inertia("Calendar", [
|
||||||
"calendar" => $calendar,
|
"calendar" => $calendar,
|
||||||
"currentMonth" => $month,
|
"currentMonth" => $month->value,
|
||||||
"users" => UserResource::collection($users),
|
"users" => UserResource::collection($users),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ use Barryvdh\DomPDF\Facade\Pdf;
|
|||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response as LaravelResponse;
|
use Illuminate\Http\Response as LaravelResponse;
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
use Toby\Domain\Enums\Role;
|
use Toby\Domain\Enums\Role;
|
||||||
use Toby\Domain\Enums\VacationType;
|
use Toby\Domain\Enums\VacationType;
|
||||||
@ -21,8 +20,10 @@ use Toby\Domain\VacationRequestStateManager;
|
|||||||
use Toby\Domain\VacationRequestStatesRetriever;
|
use Toby\Domain\VacationRequestStatesRetriever;
|
||||||
use Toby\Domain\Validation\VacationRequestValidator;
|
use Toby\Domain\Validation\VacationRequestValidator;
|
||||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
use Toby\Eloquent\Models\VacationRequest;
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
|
use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
|
||||||
|
use Toby\Infrastructure\Http\Resources\UserResource;
|
||||||
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
|
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
|
||||||
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
|
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
|
||||||
|
|
||||||
@ -61,10 +62,10 @@ class VacationRequestController extends Controller
|
|||||||
"acceptAsAdministrative" => $vacationRequest->state->canTransitionTo(AcceptedByAdministrative::class)
|
"acceptAsAdministrative" => $vacationRequest->state->canTransitionTo(AcceptedByAdministrative::class)
|
||||||
&& $user === Role::AdministrativeApprover,
|
&& $user === Role::AdministrativeApprover,
|
||||||
"reject" => $vacationRequest->state->canTransitionTo(Rejected::class)
|
"reject" => $vacationRequest->state->canTransitionTo(Rejected::class)
|
||||||
&& in_array($user->role, [Role::TechnicalApprover, Role::AdministrativeApprover]),
|
&& in_array($user->role, [Role::TechnicalApprover, Role::AdministrativeApprover], true),
|
||||||
"cancel" => $vacationRequest->state->canTransitionTo(Cancelled::class)
|
"cancel" => $vacationRequest->state->canTransitionTo(Cancelled::class)
|
||||||
&& $user === Role::AdministrativeApprover,
|
&& $user === Role::AdministrativeApprover,
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,8 +80,14 @@ class VacationRequestController extends Controller
|
|||||||
|
|
||||||
public function create(): Response
|
public function create(): Response
|
||||||
{
|
{
|
||||||
|
$users = User::query()
|
||||||
|
->orderBy("last_name")
|
||||||
|
->orderBy("first_name")
|
||||||
|
->get();
|
||||||
|
|
||||||
return inertia("VacationRequest/Create", [
|
return inertia("VacationRequest/Create", [
|
||||||
"vacationTypes" => VacationType::casesToSelect(),
|
"vacationTypes" => VacationType::casesToSelect(),
|
||||||
|
"users" => UserResource::collection($users),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +98,7 @@ class VacationRequestController extends Controller
|
|||||||
VacationDaysCalculator $vacationDaysCalculator,
|
VacationDaysCalculator $vacationDaysCalculator,
|
||||||
): RedirectResponse {
|
): RedirectResponse {
|
||||||
/** @var VacationRequest $vacationRequest */
|
/** @var VacationRequest $vacationRequest */
|
||||||
$vacationRequest = $request->user()->vacationRequests()->make($request->data());
|
$vacationRequest = $request->user()->createdVacationRequests()->make($request->data());
|
||||||
$vacationRequestValidator->validate($vacationRequest);
|
$vacationRequestValidator->validate($vacationRequest);
|
||||||
|
|
||||||
$vacationRequest->save();
|
$vacationRequest->save();
|
||||||
|
@ -16,9 +16,11 @@ class VacationRequestRequest extends FormRequest
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
"user" => ["required", "exists:users,id"],
|
||||||
"type" => ["required", new Enum(VacationType::class)],
|
"type" => ["required", new Enum(VacationType::class)],
|
||||||
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||||
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||||
|
"flowSkipped" => ["nullable", "boolean"],
|
||||||
"comment" => ["nullable"],
|
"comment" => ["nullable"],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -28,11 +30,13 @@ class VacationRequestRequest extends FormRequest
|
|||||||
$from = $this->get("from");
|
$from = $this->get("from");
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
"user_id" => $this->get("user"),
|
||||||
"type" => $this->get("type"),
|
"type" => $this->get("type"),
|
||||||
"from" => $from,
|
"from" => $from,
|
||||||
"to" => $this->get("to"),
|
"to" => $this->get("to"),
|
||||||
"year_period_id" => YearPeriod::findByYear(Carbon::create($from)->year)->id,
|
"year_period_id" => YearPeriod::findByYear(Carbon::create($from)->year)->id,
|
||||||
"comment" => $this->get("comment"),
|
"comment" => $this->get("comment"),
|
||||||
|
"flow_skipped" => $this->boolean("flowSkipped"),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class VacationRequestActivityResource extends JsonResource
|
|||||||
public function toArray($request): array
|
public function toArray($request): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"date" => $this->created_at->format("d.m.Y"),
|
"date" => $this->created_at->toDisplayDate(),
|
||||||
"time" => $this->created_at->format("H:i"),
|
"time" => $this->created_at->format("H:i"),
|
||||||
"user" => $this->user ? $this->user->fullName : __("System"),
|
"user" => $this->user ? $this->user->fullName : __("System"),
|
||||||
"state" => $this->to,
|
"state" => $this->to,
|
||||||
|
@ -19,7 +19,8 @@
|
|||||||
"laravel/tinker": "^2.5",
|
"laravel/tinker": "^2.5",
|
||||||
"lasserafn/php-initial-avatar-generator": "^4.2",
|
"lasserafn/php-initial-avatar-generator": "^4.2",
|
||||||
"spatie/laravel-google-calendar": "^3.5",
|
"spatie/laravel-google-calendar": "^3.5",
|
||||||
"spatie/laravel-model-states": "^2.1"
|
"spatie/laravel-model-states": "^2.1",
|
||||||
|
"maatwebsite/excel": "^3.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"blumilksoftware/codestyle": "^0.9.0",
|
"blumilksoftware/codestyle": "^0.9.0",
|
||||||
|
698
composer.lock
generated
698
composer.lock
generated
File diff suppressed because it is too large
Load Diff
104
config/excel.php
Normal file
104
config/excel.php
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Maatwebsite\Excel\DefaultValueBinder;
|
||||||
|
use Maatwebsite\Excel\Excel;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"exports" => [
|
||||||
|
"chunk_size" => 1000,
|
||||||
|
"pre_calculate_formulas" => false,
|
||||||
|
"strict_null_comparison" => false,
|
||||||
|
"csv" => [
|
||||||
|
"delimiter" => ",",
|
||||||
|
"enclosure" => '"',
|
||||||
|
"line_ending" => PHP_EOL,
|
||||||
|
"use_bom" => false,
|
||||||
|
"include_separator_line" => false,
|
||||||
|
"excel_compatibility" => false,
|
||||||
|
"output_encoding" => "",
|
||||||
|
],
|
||||||
|
"properties" => [
|
||||||
|
"creator" => "",
|
||||||
|
"lastModifiedBy" => "",
|
||||||
|
"title" => "",
|
||||||
|
"description" => "",
|
||||||
|
"subject" => "",
|
||||||
|
"keywords" => "",
|
||||||
|
"category" => "",
|
||||||
|
"manager" => "",
|
||||||
|
"company" => "",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
"imports" => [
|
||||||
|
"read_only" => true,
|
||||||
|
"ignore_empty" => false,
|
||||||
|
"heading_row" => [
|
||||||
|
"formatter" => "slug",
|
||||||
|
],
|
||||||
|
"csv" => [
|
||||||
|
"delimiter" => ",",
|
||||||
|
"enclosure" => '"',
|
||||||
|
"escape_character" => "\\",
|
||||||
|
"contiguous" => false,
|
||||||
|
"input_encoding" => "UTF-8",
|
||||||
|
],
|
||||||
|
"properties" => [
|
||||||
|
"creator" => "",
|
||||||
|
"lastModifiedBy" => "",
|
||||||
|
"title" => "",
|
||||||
|
"description" => "",
|
||||||
|
"subject" => "",
|
||||||
|
"keywords" => "",
|
||||||
|
"category" => "",
|
||||||
|
"manager" => "",
|
||||||
|
"company" => "",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"extension_detector" => [
|
||||||
|
"xlsx" => Excel::XLSX,
|
||||||
|
"xlsm" => Excel::XLSX,
|
||||||
|
"xltx" => Excel::XLSX,
|
||||||
|
"xltm" => Excel::XLSX,
|
||||||
|
"xls" => Excel::XLS,
|
||||||
|
"xlt" => Excel::XLS,
|
||||||
|
"ods" => Excel::ODS,
|
||||||
|
"ots" => Excel::ODS,
|
||||||
|
"slk" => Excel::SLK,
|
||||||
|
"xml" => Excel::XML,
|
||||||
|
"gnumeric" => Excel::GNUMERIC,
|
||||||
|
"htm" => Excel::HTML,
|
||||||
|
"html" => Excel::HTML,
|
||||||
|
"csv" => Excel::CSV,
|
||||||
|
"tsv" => Excel::TSV,
|
||||||
|
"pdf" => Excel::DOMPDF,
|
||||||
|
],
|
||||||
|
"value_binder" => [
|
||||||
|
"default" => DefaultValueBinder::class,
|
||||||
|
],
|
||||||
|
|
||||||
|
"cache" => [
|
||||||
|
"driver" => "memory",
|
||||||
|
"batch" => [
|
||||||
|
"memory_limit" => 60000,
|
||||||
|
],
|
||||||
|
"illuminate" => [
|
||||||
|
"store" => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"transactions" => [
|
||||||
|
"handler" => "db",
|
||||||
|
"db" => [
|
||||||
|
"connection" => null,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
"temporary_files" => [
|
||||||
|
"local_path" => storage_path("framework/cache/laravel-excel"),
|
||||||
|
"remote_disk" => null,
|
||||||
|
"remote_prefix" => null,
|
||||||
|
"force_resync_remote" => null,
|
||||||
|
],
|
||||||
|
];
|
@ -35,7 +35,7 @@ return [
|
|||||||
"name" => env("MAIL_FROM_NAME", "Example"),
|
"name" => env("MAIL_FROM_NAME", "Example"),
|
||||||
],
|
],
|
||||||
"markdown" => [
|
"markdown" => [
|
||||||
"theme" => "default",
|
"theme" => "mail",
|
||||||
"paths" => [
|
"paths" => [
|
||||||
resource_path("views/vendor/mail"),
|
resource_path("views/vendor/mail"),
|
||||||
],
|
],
|
||||||
|
@ -24,8 +24,9 @@ class VacationRequestFactory extends Factory
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
"user_id" => User::factory(),
|
"user_id" => User::factory(),
|
||||||
|
"creator_id" => fn(array $attributes): int => $attributes["user_id"],
|
||||||
"year_period_id" => YearPeriod::factory(),
|
"year_period_id" => YearPeriod::factory(),
|
||||||
"name" => fn(array $attributes) => $this->generateName($attributes),
|
"name" => fn(array $attributes): string => $this->generateName($attributes),
|
||||||
"type" => $this->faker->randomElement(VacationType::cases()),
|
"type" => $this->faker->randomElement(VacationType::cases()),
|
||||||
"state" => $this->faker->randomElement(VacationRequestStatesRetriever::all()),
|
"state" => $this->faker->randomElement(VacationRequestStatesRetriever::all()),
|
||||||
"from" => $from,
|
"from" => $from,
|
||||||
|
@ -14,6 +14,7 @@ return new class() extends Migration {
|
|||||||
Schema::create("vacation_requests", function (Blueprint $table): void {
|
Schema::create("vacation_requests", function (Blueprint $table): void {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->string("name");
|
$table->string("name");
|
||||||
|
$table->foreignIdFor(User::class, "creator_id")->constrained("users")->cascadeOnDelete();
|
||||||
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
||||||
$table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete();
|
$table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete();
|
||||||
$table->string("type");
|
$table->string("type");
|
||||||
@ -21,6 +22,7 @@ return new class() extends Migration {
|
|||||||
$table->date("from");
|
$table->date("from");
|
||||||
$table->date("to");
|
$table->date("to");
|
||||||
$table->text("comment")->nullable();
|
$table->text("comment")->nullable();
|
||||||
|
$table->boolean("flow_skipped")->default(false);
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -77,6 +77,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
VacationRequest::factory()
|
VacationRequest::factory()
|
||||||
->count(10)
|
->count(10)
|
||||||
->for($user)
|
->for($user)
|
||||||
|
->for($user, "creator")
|
||||||
->sequence(fn() => [
|
->sequence(fn() => [
|
||||||
"year_period_id" => $yearPeriods->random()->id,
|
"year_period_id" => $yearPeriods->random()->id,
|
||||||
])
|
])
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "toby",
|
"name": "application",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
12
resources/js/Composables/yearPeriodInfo.js
Normal file
12
resources/js/Composables/yearPeriodInfo.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import {computed} from 'vue'
|
||||||
|
import {usePage} from '@inertiajs/inertia-vue3'
|
||||||
|
|
||||||
|
export default function useCurrentYearPeriodInfo() {
|
||||||
|
const minDate = computed(() => new Date(usePage().props.value.years.current, 0, 1))
|
||||||
|
const maxDate = computed(() => new Date(usePage().props.value.years.current, 11, 31))
|
||||||
|
|
||||||
|
return {
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<InertiaHead title="Kalendarz urlopów" />
|
<InertiaHead title="Kalendarz urlopów" />
|
||||||
<div class="bg-white shadow-md">
|
<div class="bg-white shadow-md">
|
||||||
<div class="p-4 sm:px-6">
|
<div class="flex justify-between items-center p-4 sm:px-6">
|
||||||
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
<div>
|
||||||
Kalendarz urlopów
|
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
</h2>
|
Kalendarz urlopów
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<a
|
||||||
|
:href="`/timesheet/${selectedMonth.value}`"
|
||||||
|
class="inline-flex items-center px-4 py-3 border border-transparent text-sm leading-4 font-medium rounded-md shadow-sm text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
|
>
|
||||||
|
Pobierz plik excel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<table class="w-full text-center table-fixed text-sm border border-gray-300">
|
<table class="w-full text-center table-fixed text-sm border border-gray-300">
|
||||||
@ -19,7 +29,7 @@
|
|||||||
<MenuButton
|
<MenuButton
|
||||||
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
>
|
>
|
||||||
{{ selectedMonth.name }}
|
{{ selectedMonth.name }} {{ years.current }}
|
||||||
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" />
|
<ChevronDownIcon class="-mr-1 ml-2 h-5 w-5" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
@ -42,8 +52,7 @@
|
|||||||
v-slot="{ active }"
|
v-slot="{ active }"
|
||||||
>
|
>
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
href="/vacation-calendar"
|
:href="`/vacation-calendar/${month.value}`"
|
||||||
:data="{ month: month.value }"
|
|
||||||
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex w-full font-normal px-4 py-2 text-sm']"
|
:class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex w-full font-normal px-4 py-2 text-sm']"
|
||||||
>
|
>
|
||||||
{{ month.name }}
|
{{ month.name }}
|
||||||
@ -151,6 +160,10 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: () => 'january',
|
default: () => 'january',
|
||||||
},
|
},
|
||||||
|
years: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const {getMonths, findMonth} = useMonthInfo()
|
const {getMonths, findMonth} = useMonthInfo()
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
id="date"
|
id="date"
|
||||||
v-model="form.date"
|
v-model="form.date"
|
||||||
placeholder="Wybierz datę"
|
placeholder="Wybierz datę"
|
||||||
|
:config="{minDate, maxDate}"
|
||||||
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
|
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
|
||||||
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }"
|
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.date, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.date }"
|
||||||
/>
|
/>
|
||||||
@ -81,8 +82,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useForm } from '@inertiajs/inertia-vue3'
|
import {useForm} from '@inertiajs/inertia-vue3'
|
||||||
import FlatPickr from 'vue-flatpickr-component'
|
import FlatPickr from 'vue-flatpickr-component'
|
||||||
|
import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HolidayCreate',
|
name: 'HolidayCreate',
|
||||||
@ -95,7 +97,13 @@ export default {
|
|||||||
date: null,
|
date: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { form }
|
const {minDate, maxDate} = useCurrentYearPeriodInfo()
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createHoliday() {
|
createHoliday() {
|
||||||
|
@ -28,6 +28,76 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Listbox
|
||||||
|
v-model="form.user"
|
||||||
|
as="div"
|
||||||
|
class="sm:grid sm:grid-cols-3 py-4 items-center"
|
||||||
|
>
|
||||||
|
<ListboxLabel class="block text-sm font-medium text-gray-700">
|
||||||
|
Osoba składająca wniosek
|
||||||
|
</ListboxLabel>
|
||||||
|
<div class="mt-1 relative sm:mt-0 sm:col-span-2">
|
||||||
|
<ListboxButton
|
||||||
|
class="bg-white relative w-full max-w-lg border rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default sm:text-sm focus:ring-1"
|
||||||
|
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.type, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.type }"
|
||||||
|
>
|
||||||
|
<span class="flex items-center">
|
||||||
|
<img
|
||||||
|
:src="form.user.avatar"
|
||||||
|
class="flex-shrink-0 h-6 w-6 rounded-full"
|
||||||
|
>
|
||||||
|
<span class="ml-3 block truncate">{{ form.user.name }}</span>
|
||||||
|
</span>
|
||||||
|
<span class="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
|
||||||
|
<SelectorIcon class="h-5 w-5 text-gray-400" />
|
||||||
|
</span>
|
||||||
|
</ListboxButton>
|
||||||
|
|
||||||
|
<transition
|
||||||
|
leave-active-class="transition ease-in duration-100"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<ListboxOptions
|
||||||
|
class="absolute z-10 mt-1 w-full max-w-lg bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"
|
||||||
|
>
|
||||||
|
<ListboxOption
|
||||||
|
v-for="user in users.data"
|
||||||
|
:key="user.id"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
as="template"
|
||||||
|
:value="user"
|
||||||
|
>
|
||||||
|
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<img
|
||||||
|
:src="user.avatar"
|
||||||
|
alt=""
|
||||||
|
class="flex-shrink-0 h-6 w-6 rounded-full"
|
||||||
|
>
|
||||||
|
<span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']">
|
||||||
|
{{ user.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="selected"
|
||||||
|
:class="[active ? 'text-white' : 'text-blumilk-600', 'absolute inset-y-0 right-0 flex items-center pr-4']"
|
||||||
|
>
|
||||||
|
<CheckIcon class="h-5 w-5" />
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ListboxOption>
|
||||||
|
</ListboxOptions>
|
||||||
|
</transition>
|
||||||
|
<p
|
||||||
|
v-if="form.errors.type"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.type }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
<Listbox
|
<Listbox
|
||||||
v-model="form.type"
|
v-model="form.type"
|
||||||
as="div"
|
as="div"
|
||||||
@ -161,6 +231,25 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
|
||||||
|
<label
|
||||||
|
for="flowSkipped"
|
||||||
|
class="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Natychmiastowo zatwierdź wniosek
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<Switch
|
||||||
|
id="flowSkipped"
|
||||||
|
v-model="form.flowSkipped"
|
||||||
|
:class="[form.flowSkipped ? 'bg-blumilk-500' : 'bg-gray-200', 'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500']"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[form.flowSkipped ? 'translate-x-5' : 'translate-x-0', 'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200']"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="flex justify-end py-3">
|
<div class="flex justify-end py-3">
|
||||||
<div class="space-x-3">
|
<div class="space-x-3">
|
||||||
<InertiaLink
|
<InertiaLink
|
||||||
@ -183,16 +272,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {useForm, usePage} from '@inertiajs/inertia-vue3'
|
import {useForm} from '@inertiajs/inertia-vue3'
|
||||||
import FlatPickr from 'vue-flatpickr-component'
|
import FlatPickr from 'vue-flatpickr-component'
|
||||||
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue'
|
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions, Switch} from '@headlessui/vue'
|
||||||
import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid'
|
import {CheckIcon, SelectorIcon, XCircleIcon} from '@heroicons/vue/solid'
|
||||||
import {reactive, ref, computed} from 'vue'
|
import {reactive, ref} from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import useCurrentYearPeriodInfo from '@/Composables/yearPeriodInfo'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VacationRequestCreate',
|
name: 'VacationRequestCreate',
|
||||||
components: {
|
components: {
|
||||||
|
Switch,
|
||||||
FlatPickr,
|
FlatPickr,
|
||||||
Listbox,
|
Listbox,
|
||||||
ListboxButton,
|
ListboxButton,
|
||||||
@ -204,6 +295,14 @@ export default {
|
|||||||
XCircleIcon,
|
XCircleIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
auth: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
vacationTypes: {
|
vacationTypes: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => null,
|
default: () => null,
|
||||||
@ -215,15 +314,16 @@ export default {
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
|
user: props.users.data.find(user => user.id === props.auth.user.id),
|
||||||
from: null,
|
from: null,
|
||||||
to: null,
|
to: null,
|
||||||
type: props.vacationTypes[0],
|
type: props.vacationTypes[0],
|
||||||
comment: null,
|
comment: null,
|
||||||
|
flowSkipped: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const estimatedDays = ref([])
|
const estimatedDays = ref([])
|
||||||
const minDate = computed(() => new Date(usePage().props.value.years.current, 0, 1))
|
const {minDate, maxDate} = useCurrentYearPeriodInfo()
|
||||||
const maxDate = computed(() => new Date(usePage().props.value.years.current, 11, 31))
|
|
||||||
|
|
||||||
const disableDates = [
|
const disableDates = [
|
||||||
date => (date.getDay() === 0 || date.getDay() === 6),
|
date => (date.getDay() === 0 || date.getDay() === 6),
|
||||||
@ -254,6 +354,7 @@ export default {
|
|||||||
.transform(data => ({
|
.transform(data => ({
|
||||||
...data,
|
...data,
|
||||||
type: data.type.value,
|
type: data.type.value,
|
||||||
|
user: data.user.id,
|
||||||
}))
|
}))
|
||||||
.post('/vacation-requests')
|
.post('/vacation-requests')
|
||||||
},
|
},
|
||||||
|
@ -42,7 +42,35 @@
|
|||||||
"Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.",
|
"Vacation limits have been updated.": "Limity urlopów zostały zaktualizowane.",
|
||||||
"Vacation request has been created.": "Wniosek urlopowy został utworzony.",
|
"Vacation request has been created.": "Wniosek urlopowy został utworzony.",
|
||||||
"Vacation request has been accepted.": "Wniosek urlopowy został zaakceptowany.",
|
"Vacation request has been accepted.": "Wniosek urlopowy został zaakceptowany.",
|
||||||
|
"Vacation request has been approved.": "Wniosek urlopowy został zatwierdzony.",
|
||||||
"Vacation request has been rejected.": "Wniosek urlopowy został odrzucony.",
|
"Vacation request has been rejected.": "Wniosek urlopowy został odrzucony.",
|
||||||
"Vacation request has been cancelled.": "Wniosek urlopowy został anulowany."
|
"Vacation request has been cancelled.": "Wniosek urlopowy został anulowany.",
|
||||||
|
"Sum:": "Suma:",
|
||||||
|
"Date": "Data",
|
||||||
|
"Day of week": "Dzień tygodnia",
|
||||||
|
"Start date": "Data rozpoczęcia",
|
||||||
|
"End date": "Data zakończenia",
|
||||||
|
"Worked hours": "Liczba godzin",
|
||||||
|
"Hi :user!": "Cześć :user!",
|
||||||
|
"The vacation request :title has changed state to :state.": "Wniosek urlopowy :title zmienił status na :state.",
|
||||||
|
"Vacation request :title": "Wniosek urlopowy :title",
|
||||||
|
"Regards": "Z poważaniem",
|
||||||
|
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Jeżeli masz problemy z kliknięciem przycisku \":actionText\", skopiuj i wklej poniższy adres w pasek przeglądarki:",
|
||||||
|
"All rights reserved.": "Wszelkie prawa zastrzeżone",
|
||||||
|
"Show vacation request": "Pokaż wniosek",
|
||||||
|
"Vacation request :title has been created" : "Wniosek :title został utworzony",
|
||||||
|
"The vacation request :title has been created correctly in the :appName.": "W systemie :appName został poprawnie utworzony wniosek urlopowy :title.",
|
||||||
|
"Vacation type: :type": "Rodzaj wniosku: :type",
|
||||||
|
"From :from to :to (number of days: :days)": "Od :from do :to (liczba dni: :days)",
|
||||||
|
"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 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."
|
||||||
}
|
}
|
||||||
|
286
resources/views/vendor/mail/html/themes/mail.css
vendored
Normal file
286
resources/views/vendor/mail/html/themes/mail.css
vendored
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
/* Base */
|
||||||
|
|
||||||
|
body, body *:not(html):not(style):not(br):not(tr):not(code) {
|
||||||
|
font-family: Avenir, Helvetica, sans-serif;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #f5f8fa;
|
||||||
|
color: #74787e;
|
||||||
|
height: 100%;
|
||||||
|
hyphens: auto;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
-moz-hyphens: auto;
|
||||||
|
-ms-word-break: break-all;
|
||||||
|
width: 100% !important;
|
||||||
|
-webkit-hyphens: auto;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
word-break: break-all;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
ul,
|
||||||
|
ol,
|
||||||
|
blockquote {
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #3c5f97;
|
||||||
|
}
|
||||||
|
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #2F3133;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #74787e;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5em;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sub {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
background-color: #f5f8fa;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 25px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header a {
|
||||||
|
color: #3c5f97;
|
||||||
|
font-size: 19px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
text-shadow: 0 1px 0 #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body */
|
||||||
|
|
||||||
|
.body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
border-bottom: 1px solid #edeff2;
|
||||||
|
border-top: 1px solid #edeff2;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-body {
|
||||||
|
background-color: #ffffff;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
width: 570px;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 570px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Subcopy */
|
||||||
|
|
||||||
|
.subcopy {
|
||||||
|
border-top: 1px solid #edeff2;
|
||||||
|
margin-top: 25px;
|
||||||
|
padding-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subcopy p {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 570px;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 570px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer p {
|
||||||
|
color: #aeaeae;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables */
|
||||||
|
|
||||||
|
.table table {
|
||||||
|
margin: 30px auto;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table th {
|
||||||
|
color: #74787e;
|
||||||
|
border-bottom: 1px solid #edeff2;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table td {
|
||||||
|
color: #74787e;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-cell {
|
||||||
|
padding: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
|
||||||
|
.action {
|
||||||
|
margin: 30px auto;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border-radius: 3px;
|
||||||
|
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
|
||||||
|
color: #ffffff;
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
-webkit-text-size-adjust: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-blue, .button-primary {
|
||||||
|
background-color: #3c5f97;
|
||||||
|
border-top: 10px solid #3c5f97;
|
||||||
|
border-right: 18px solid #3c5f97;
|
||||||
|
border-bottom: 10px solid #3c5f97;
|
||||||
|
border-left: 18px solid #3c5f97;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-green, .button-success {
|
||||||
|
background-color: #22c55e;
|
||||||
|
border-top: 10px solid #22c55e;
|
||||||
|
border-right: 18px solid #22c55e;
|
||||||
|
border-bottom: 10px solid #22c55e;
|
||||||
|
border-left: 18px solid #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-red, .button-error {
|
||||||
|
background-color: #ef4444;
|
||||||
|
border-top: 10px solid #ef4444;
|
||||||
|
border-right: 18px solid #ef4444;
|
||||||
|
border-bottom: 10px solid #ef4444;
|
||||||
|
border-left: 18px solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panels */
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
margin: 0 0 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-content {
|
||||||
|
background-color: #edeff2;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-item p:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Promotions */
|
||||||
|
|
||||||
|
.promotion {
|
||||||
|
background-color: #FFFFFF;
|
||||||
|
border: 2px dashed #9BA2AB;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
margin-top: 25px;
|
||||||
|
padding: 24px;
|
||||||
|
width: 100%;
|
||||||
|
-premailer-cellpadding: 0;
|
||||||
|
-premailer-cellspacing: 0;
|
||||||
|
-premailer-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promotion h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.promotion p {
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -7,6 +7,7 @@ use Toby\Infrastructure\Http\Controllers\GoogleController;
|
|||||||
use Toby\Infrastructure\Http\Controllers\HolidayController;
|
use Toby\Infrastructure\Http\Controllers\HolidayController;
|
||||||
use Toby\Infrastructure\Http\Controllers\LogoutController;
|
use Toby\Infrastructure\Http\Controllers\LogoutController;
|
||||||
use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController;
|
use Toby\Infrastructure\Http\Controllers\SelectYearPeriodController;
|
||||||
|
use Toby\Infrastructure\Http\Controllers\TimesheetController;
|
||||||
use Toby\Infrastructure\Http\Controllers\UserController;
|
use Toby\Infrastructure\Http\Controllers\UserController;
|
||||||
use Toby\Infrastructure\Http\Controllers\VacationCalendarController;
|
use Toby\Infrastructure\Http\Controllers\VacationCalendarController;
|
||||||
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
||||||
@ -24,8 +25,10 @@ Route::middleware("auth")->group(function (): void {
|
|||||||
|
|
||||||
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])
|
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])
|
||||||
->name("vacation.limits");
|
->name("vacation.limits");
|
||||||
Route::get("/vacation-calendar", [VacationCalendarController::class, "index"])
|
Route::get("/vacation-calendar/{month?}", [VacationCalendarController::class, "index"])
|
||||||
->name("vacation.calendar");
|
->name("vacation.calendar");
|
||||||
|
Route::get("/timesheet/{month}", TimesheetController::class)
|
||||||
|
->name("timesheet");
|
||||||
|
|
||||||
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits");
|
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])->name("vacation.limits");
|
||||||
Route::put("/vacation-limits", [VacationLimitController::class, "update"]);
|
Route::put("/vacation-limits", [VacationLimitController::class, "update"]);
|
||||||
|
@ -6,10 +6,14 @@ namespace Tests\Feature;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Bus;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Inertia\Testing\AssertableInertia as Assert;
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
use Tests\FeatureTestCase;
|
use Tests\FeatureTestCase;
|
||||||
use Toby\Domain\Enums\VacationType;
|
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\PolishHolidaysRetriever;
|
||||||
use Toby\Domain\States\VacationRequest\Approved;
|
use Toby\Domain\States\VacationRequest\Approved;
|
||||||
use Toby\Domain\States\VacationRequest\Rejected;
|
use Toby\Domain\States\VacationRequest\Rejected;
|
||||||
@ -19,7 +23,6 @@ use Toby\Eloquent\Models\User;
|
|||||||
use Toby\Eloquent\Models\VacationLimit;
|
use Toby\Eloquent\Models\VacationLimit;
|
||||||
use Toby\Eloquent\Models\VacationRequest;
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
use Toby\Eloquent\Models\YearPeriod;
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar;
|
|
||||||
|
|
||||||
class VacationRequestTest extends FeatureTestCase
|
class VacationRequestTest extends FeatureTestCase
|
||||||
{
|
{
|
||||||
@ -31,8 +34,6 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
Bus::fake();
|
|
||||||
|
|
||||||
$this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class);
|
$this->polishHolidaysRetriever = $this->app->make(PolishHolidaysRetriever::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +73,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
@ -91,8 +93,87 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUserCanCreateVacationRequestOnEmployeeBehalf(): void
|
||||||
|
{
|
||||||
|
$creator = User::factory()->createQuietly();
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($creator)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"user_id" => $user->id,
|
||||||
|
"creator_id" => $creator->id,
|
||||||
|
"year_period_id" => $currentYearPeriod->id,
|
||||||
|
"name" => "1/" . $currentYearPeriod->year,
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => WaitingForTechnical::$name,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUserCanCreateVacationRequestOnEmployeeBehalfAndSkipAcceptanceFlow(): void
|
||||||
|
{
|
||||||
|
Event::fake(VacationRequestApproved::class);
|
||||||
|
|
||||||
|
$creator = User::factory()->createQuietly();
|
||||||
|
$user = User::factory()->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
VacationLimit::factory([
|
||||||
|
"days" => 20,
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->actingAs($creator)
|
||||||
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
"flowSkipped" => true,
|
||||||
|
])
|
||||||
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
|
"user_id" => $user->id,
|
||||||
|
"creator_id" => $creator->id,
|
||||||
|
"year_period_id" => $currentYearPeriod->id,
|
||||||
|
"name" => "1/" . $currentYearPeriod->year,
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => Approved::$name,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function testTechnicalApproverCanApproveVacationRequest(): void
|
public function testTechnicalApproverCanApproveVacationRequest(): void
|
||||||
{
|
{
|
||||||
|
Event::fake(VacationRequestAcceptedByTechnical::class);
|
||||||
|
|
||||||
$user = User::factory()->createQuietly();
|
$user = User::factory()->createQuietly();
|
||||||
$technicalApprover = User::factory()->createQuietly();
|
$technicalApprover = User::factory()->createQuietly();
|
||||||
$currentYearPeriod = YearPeriod::current();
|
$currentYearPeriod = YearPeriod::current();
|
||||||
@ -109,13 +190,13 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical")
|
->post("/vacation-requests/{$vacationRequest->id}/accept-as-technical")
|
||||||
->assertSessionHasNoErrors();
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
$this->assertDatabaseHas("vacation_requests", [
|
Event::assertDispatched(VacationRequestAcceptedByTechnical::class);
|
||||||
"state" => WaitingForAdministrative::$name,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAdministrativeApproverCanApproveVacationRequest(): void
|
public function testAdministrativeApproverCanApproveVacationRequest(): void
|
||||||
{
|
{
|
||||||
|
Event::fake(VacationRequestAcceptedByAdministrative::class);
|
||||||
|
|
||||||
$user = User::factory()->createQuietly();
|
$user = User::factory()->createQuietly();
|
||||||
$administrativeApprover = User::factory()->createQuietly();
|
$administrativeApprover = User::factory()->createQuietly();
|
||||||
|
|
||||||
@ -132,15 +213,13 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative")
|
->post("/vacation-requests/{$vacationRequest->id}/accept-as-administrative")
|
||||||
->assertSessionHasNoErrors();
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
$this->assertDatabaseHas("vacation_requests", [
|
Event::assertDispatched(VacationRequestAcceptedByAdministrative::class);
|
||||||
"state" => Approved::$name,
|
|
||||||
]);
|
|
||||||
|
|
||||||
Bus::assertDispatched(SendVacationRequestDaysToGoogleCalendar::class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTechnicalApproverCanRejectVacationRequest(): void
|
public function testTechnicalApproverCanRejectVacationRequest(): void
|
||||||
{
|
{
|
||||||
|
Event::fake(VacationRequestRejected::class);
|
||||||
|
|
||||||
$user = User::factory()->createQuietly();
|
$user = User::factory()->createQuietly();
|
||||||
$technicalApprover = User::factory()->createQuietly();
|
$technicalApprover = User::factory()->createQuietly();
|
||||||
$currentYearPeriod = YearPeriod::current();
|
$currentYearPeriod = YearPeriod::current();
|
||||||
@ -164,6 +243,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
->post("/vacation-requests/{$vacationRequest->id}/reject")
|
->post("/vacation-requests/{$vacationRequest->id}/reject")
|
||||||
->assertSessionHasNoErrors();
|
->assertSessionHasNoErrors();
|
||||||
|
|
||||||
|
Event::assertDispatched(VacationRequestRejected::class);
|
||||||
$this->assertDatabaseHas("vacation_requests", [
|
$this->assertDatabaseHas("vacation_requests", [
|
||||||
"state" => Rejected::$name,
|
"state" => Rejected::$name,
|
||||||
]);
|
]);
|
||||||
@ -183,6 +263,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 11)->toDateString(),
|
||||||
@ -207,6 +288,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 5)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 5)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
||||||
@ -238,6 +320,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 4, 18)->toDateString(),
|
||||||
@ -273,6 +356,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
@ -280,8 +364,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
])
|
])
|
||||||
->assertSessionHasErrors([
|
->assertSessionHasErrors([
|
||||||
"vacationRequest" => __("You have pending vacation request in this range."),
|
"vacationRequest" => __("You have pending vacation request in this range."),
|
||||||
])
|
]);
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testUserCannotCreateVacationRequestIfHeHasApprovedVacationRequestInThisRange(): void
|
public function testUserCannotCreateVacationRequestIfHeHasApprovedVacationRequestInThisRange(): void
|
||||||
@ -309,6 +392,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
|
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
@ -325,6 +409,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
$currentYearPeriod = YearPeriod::current();
|
$currentYearPeriod = YearPeriod::current();
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 2, 7)->toDateString(),
|
||||||
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
"to" => Carbon::create($currentYearPeriod->year, 2, 6)->toDateString(),
|
||||||
@ -342,6 +427,7 @@ class VacationRequestTest extends FeatureTestCase
|
|||||||
$nextYearPeriod = $this->createYearPeriod(Carbon::now()->year + 1);
|
$nextYearPeriod = $this->createYearPeriod(Carbon::now()->year + 1);
|
||||||
$this->actingAs($user)
|
$this->actingAs($user)
|
||||||
->post("/vacation-requests", [
|
->post("/vacation-requests", [
|
||||||
|
"user" => $user->id,
|
||||||
"type" => VacationType::Vacation->value,
|
"type" => VacationType::Vacation->value,
|
||||||
"from" => Carbon::create($currentYearPeriod->year, 12, 27)->toDateString(),
|
"from" => Carbon::create($currentYearPeriod->year, 12, 27)->toDateString(),
|
||||||
"to" => Carbon::create($nextYearPeriod->year, 1, 2)->toDateString(),
|
"to" => Carbon::create($nextYearPeriod->year, 1, 2)->toDateString(),
|
||||||
|
70
tests/Unit/VacationRequestNotificationTest.php
Normal file
70
tests/Unit/VacationRequestNotificationTest.php
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?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\Role;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\Notifications\VacationRequestWaitsForTechApprovalNotification;
|
||||||
|
use Toby\Domain\States\VacationRequest\Created;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAfterChangingVacationRequestStateNotificationAreSentToUsers(): void
|
||||||
|
{
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
|
$user = User::factory([
|
||||||
|
"role" => Role::Employee,
|
||||||
|
])->createQuietly();
|
||||||
|
$technicalApprover = User::factory([
|
||||||
|
"role" => Role::TechnicalApprover,
|
||||||
|
])->createQuietly();
|
||||||
|
$administrativeApprover = User::factory([
|
||||||
|
"role" => Role::AdministrativeApprover,
|
||||||
|
])->createQuietly();
|
||||||
|
|
||||||
|
$currentYearPeriod = YearPeriod::current();
|
||||||
|
|
||||||
|
/** @var VacationRequest $vacationRequest */
|
||||||
|
$vacationRequest = VacationRequest::factory([
|
||||||
|
"type" => VacationType::Vacation->value,
|
||||||
|
"state" => Created::class,
|
||||||
|
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
|
||||||
|
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
|
||||||
|
"comment" => "Comment for the vacation request.",
|
||||||
|
])
|
||||||
|
->for($user)
|
||||||
|
->for($currentYearPeriod)
|
||||||
|
->create();
|
||||||
|
|
||||||
|
$this->stateManager->waitForTechnical($vacationRequest);
|
||||||
|
|
||||||
|
Notification::assertSentTo($technicalApprover, VacationRequestWaitsForTechApprovalNotification::class);
|
||||||
|
Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestWaitsForTechApprovalNotification::class);
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ namespace Tests\Unit;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
use Illuminate\Foundation\Testing\DatabaseMigrations;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Notification;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
use Tests\Traits\InteractsWithYearPeriods;
|
use Tests\Traits\InteractsWithYearPeriods;
|
||||||
use Toby\Domain\Enums\VacationType;
|
use Toby\Domain\Enums\VacationType;
|
||||||
@ -28,6 +29,8 @@ class VacationRequestStatesTest extends TestCase
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
Notification::fake();
|
||||||
|
|
||||||
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
|
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
|
||||||
|
|
||||||
$this->createCurrentYearPeriod();
|
$this->createCurrentYearPeriod();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user