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:
@@ -15,6 +15,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
public function boot(): void
|
||||
{
|
||||
Carbon::macro("toDisplayString", fn() => $this->translatedFormat("j F Y"));
|
||||
Carbon::macro("toDisplayDate", fn() => $this->translatedFormat("d.m.Y"));
|
||||
|
||||
$selectedYearPeriodScope = $this->app->make(SelectedYearPeriodScope::class);
|
||||
|
||||
|
@@ -10,22 +10,34 @@ use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||
use Toby\Domain\Events\VacationRequestApproved;
|
||||
use Toby\Domain\Events\VacationRequestCancelled;
|
||||
use Toby\Domain\Events\VacationRequestCreated;
|
||||
use Toby\Domain\Events\VacationRequestRejected;
|
||||
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||
use Toby\Domain\Events\VacationRequestWaitsForAdminApproval;
|
||||
use Toby\Domain\Events\VacationRequestWaitsForTechApproval;
|
||||
use Toby\Domain\Listeners\CreateVacationRequestActivity;
|
||||
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
|
||||
use Toby\Domain\Listeners\HandleCancelledVacationRequest;
|
||||
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
|
||||
{
|
||||
protected $listen = [
|
||||
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
|
||||
VacationRequestCreated::class => [HandleCreatedVacationRequest::class],
|
||||
VacationRequestCreated::class => [HandleCreatedVacationRequest::class, SendCreatedVacationRequestNotification::class],
|
||||
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
|
||||
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
|
||||
VacationRequestApproved::class => [HandleApprovedVacationRequest::class],
|
||||
VacationRequestCancelled::class => [HandleCancelledVacationRequest::class],
|
||||
VacationRequestApproved::class => [HandleApprovedVacationRequest::class, SendApprovedVacationRequestNotification::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;
|
||||
|
||||
use Carbon\CarbonImmutable;
|
||||
use Carbon\CarbonInterface;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||
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($date->startOfMonth(), $date->endOfMonth());
|
||||
$period = CarbonPeriod::create($month->copy()->startOfMonth(), $month->copy()->endOfMonth());
|
||||
$yearPeriod = YearPeriod::findByYear($month->year);
|
||||
|
||||
$holidays = $yearPeriod->holidays()->pluck("date");
|
||||
|
||||
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
|
||||
{
|
||||
$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;
|
||||
|
||||
if ($vacationRequest->hasFlowSkipped()) {
|
||||
$this->stateManager->approve($vacationRequest);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
|
||||
$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\VacationRequestCancelled;
|
||||
use Toby\Domain\Events\VacationRequestCreated;
|
||||
use Toby\Domain\Events\VacationRequestRejected;
|
||||
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\AcceptedByTechnical;
|
||||
use Toby\Domain\States\VacationRequest\Approved;
|
||||
@@ -48,6 +51,7 @@ class VacationRequestStateManager
|
||||
public function reject(VacationRequest $vacationRequest, ?User $user = null): void
|
||||
{
|
||||
$this->changeState($vacationRequest, Rejected::class, $user);
|
||||
$this->dispatcher->dispatch(new VacationRequestRejected($vacationRequest));
|
||||
}
|
||||
|
||||
public function cancel(VacationRequest $vacationRequest, ?User $user = null): void
|
||||
@@ -74,11 +78,15 @@ class VacationRequestStateManager
|
||||
public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void
|
||||
{
|
||||
$this->changeState($vacationRequest, WaitingForTechnical::class, $user);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest));
|
||||
}
|
||||
|
||||
public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
|
||||
{
|
||||
$this->changeState($vacationRequest, WaitingForAdministrative::class, $user);
|
||||
|
||||
$this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest));
|
||||
}
|
||||
|
||||
protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
|
||||
|
@@ -58,6 +58,11 @@ class User extends Authenticatable
|
||||
return $this->hasMany(VacationRequest::class);
|
||||
}
|
||||
|
||||
public function createdVacationRequests(): HasMany
|
||||
{
|
||||
return $this->hasMany(VacationRequest::class, "creator_id");
|
||||
}
|
||||
|
||||
public function vacations(): HasMany
|
||||
{
|
||||
return $this->hasMany(Vacation::class);
|
||||
|
@@ -23,7 +23,9 @@ use Toby\Domain\States\VacationRequest\VacationRequestState;
|
||||
* @property Carbon $from
|
||||
* @property Carbon $to
|
||||
* @property string $comment
|
||||
* @property bool $flow_skipped
|
||||
* @property User $user
|
||||
* @property User $creator
|
||||
* @property YearPeriod $yearPeriod
|
||||
* @property Collection $activities
|
||||
* @property Collection $vacations
|
||||
@@ -49,6 +51,11 @@ class VacationRequest extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, "creator_id");
|
||||
}
|
||||
|
||||
public function yearPeriod(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(YearPeriod::class);
|
||||
@@ -80,6 +87,11 @@ class VacationRequest extends Model
|
||||
->where("to", ">=", $vacationRequest->from);
|
||||
}
|
||||
|
||||
public function hasFlowSkipped(): bool
|
||||
{
|
||||
return $this->flow_skipped;
|
||||
}
|
||||
|
||||
protected static function newFactory(): VacationRequestFactory
|
||||
{
|
||||
return VacationRequestFactory::new();
|
||||
|
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Toby\Eloquent\Observers;
|
||||
|
||||
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||
use Illuminate\Events\Dispatcher;
|
||||
use Illuminate\Contracts\Events\Dispatcher;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
|
||||
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;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Response;
|
||||
use Toby\Domain\CalendarGenerator;
|
||||
use Toby\Domain\Enums\Month;
|
||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Infrastructure\Http\Resources\UserResource;
|
||||
@@ -16,22 +15,25 @@ use Toby\Infrastructure\Http\Resources\UserResource;
|
||||
class VacationCalendarController extends Controller
|
||||
{
|
||||
public function index(
|
||||
Request $request,
|
||||
YearPeriodRetriever $yearPeriodRetriever,
|
||||
CalendarGenerator $calendarGenerator,
|
||||
?string $month = null,
|
||||
): Response {
|
||||
$month = Str::lower($request->query("month", Carbon::now()->englishMonth));
|
||||
$month = Month::fromNameOrCurrent((string)$month);
|
||||
|
||||
$yearPeriod = $yearPeriodRetriever->selected();
|
||||
$carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber());
|
||||
|
||||
$users = User::query()
|
||||
->orderBy("last_name")
|
||||
->orderBy("first_name")
|
||||
->get();
|
||||
|
||||
$calendar = $calendarGenerator->generate($yearPeriod, $month);
|
||||
$calendar = $calendarGenerator->generate($carbonMonth);
|
||||
|
||||
return inertia("Calendar", [
|
||||
"calendar" => $calendar,
|
||||
"currentMonth" => $month,
|
||||
"currentMonth" => $month->value,
|
||||
"users" => UserResource::collection($users),
|
||||
]);
|
||||
}
|
||||
|
@@ -8,7 +8,6 @@ use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response as LaravelResponse;
|
||||
use Illuminate\Support\Arr;
|
||||
use Inertia\Response;
|
||||
use Toby\Domain\Enums\Role;
|
||||
use Toby\Domain\Enums\VacationType;
|
||||
@@ -21,8 +20,10 @@ use Toby\Domain\VacationRequestStateManager;
|
||||
use Toby\Domain\VacationRequestStatesRetriever;
|
||||
use Toby\Domain\Validation\VacationRequestValidator;
|
||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
|
||||
use Toby\Eloquent\Models\User;
|
||||
use Toby\Eloquent\Models\VacationRequest;
|
||||
use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
|
||||
use Toby\Infrastructure\Http\Resources\UserResource;
|
||||
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
|
||||
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
|
||||
|
||||
@@ -61,10 +62,10 @@ class VacationRequestController extends Controller
|
||||
"acceptAsAdministrative" => $vacationRequest->state->canTransitionTo(AcceptedByAdministrative::class)
|
||||
&& $user === Role::AdministrativeApprover,
|
||||
"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)
|
||||
&& $user === Role::AdministrativeApprover,
|
||||
]
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -79,8 +80,14 @@ class VacationRequestController extends Controller
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$users = User::query()
|
||||
->orderBy("last_name")
|
||||
->orderBy("first_name")
|
||||
->get();
|
||||
|
||||
return inertia("VacationRequest/Create", [
|
||||
"vacationTypes" => VacationType::casesToSelect(),
|
||||
"users" => UserResource::collection($users),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -91,7 +98,7 @@ class VacationRequestController extends Controller
|
||||
VacationDaysCalculator $vacationDaysCalculator,
|
||||
): RedirectResponse {
|
||||
/** @var VacationRequest $vacationRequest */
|
||||
$vacationRequest = $request->user()->vacationRequests()->make($request->data());
|
||||
$vacationRequest = $request->user()->createdVacationRequests()->make($request->data());
|
||||
$vacationRequestValidator->validate($vacationRequest);
|
||||
|
||||
$vacationRequest->save();
|
||||
|
@@ -16,9 +16,11 @@ class VacationRequestRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
"user" => ["required", "exists:users,id"],
|
||||
"type" => ["required", new Enum(VacationType::class)],
|
||||
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||
"flowSkipped" => ["nullable", "boolean"],
|
||||
"comment" => ["nullable"],
|
||||
];
|
||||
}
|
||||
@@ -28,11 +30,13 @@ class VacationRequestRequest extends FormRequest
|
||||
$from = $this->get("from");
|
||||
|
||||
return [
|
||||
"user_id" => $this->get("user"),
|
||||
"type" => $this->get("type"),
|
||||
"from" => $from,
|
||||
"to" => $this->get("to"),
|
||||
"year_period_id" => YearPeriod::findByYear(Carbon::create($from)->year)->id,
|
||||
"comment" => $this->get("comment"),
|
||||
"flow_skipped" => $this->boolean("flowSkipped"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@ class VacationRequestActivityResource extends JsonResource
|
||||
public function toArray($request): array
|
||||
{
|
||||
return [
|
||||
"date" => $this->created_at->format("d.m.Y"),
|
||||
"date" => $this->created_at->toDisplayDate(),
|
||||
"time" => $this->created_at->format("H:i"),
|
||||
"user" => $this->user ? $this->user->fullName : __("System"),
|
||||
"state" => $this->to,
|
||||
|
Reference in New Issue
Block a user