Merge branch 'main' into #22-vacation-calendar
# Conflicts: # resources/js/Pages/Dashboard.vue # resources/js/Shared/Layout/AppLayout.vue # resources/js/Shared/MainMenu.vue # routes/web.php
This commit is contained in:
commit
47288917a2
10
.eslintrc.js
10
.eslintrc.js
@ -1,17 +1,17 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
|
'vue/setup-compiler-macros': true,
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:vue/vue3-recommended',
|
'plugin:vue/vue3-recommended',
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
semi: [2, 'always'],
|
semi: [2, 'never'],
|
||||||
quotes: ['error', 'single'],
|
quotes: ['error', 'single'],
|
||||||
indent: ['error', 4],
|
indent: ['error', 2],
|
||||||
'vue/html-indent': ['error', 4],
|
'vue/html-indent': ['error', 2],
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
'comma-dangle': ['error', 'always-multiline'],
|
'comma-dangle': ['error', 'always-multiline'],
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
@ -5,8 +5,24 @@ declare(strict_types=1);
|
|||||||
namespace Toby\Architecture\Providers;
|
namespace Toby\Architecture\Providers;
|
||||||
|
|
||||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||||
|
use Toby\Domain\Events\VacationRequestApproved;
|
||||||
|
use Toby\Domain\Events\VacationRequestCreated;
|
||||||
|
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||||
|
use Toby\Domain\Listeners\CreateVacationRequestActivity;
|
||||||
|
use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest;
|
||||||
|
use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest;
|
||||||
|
use Toby\Domain\Listeners\HandleApprovedVacationRequest;
|
||||||
|
use Toby\Domain\Listeners\HandleCreatedVacationRequest;
|
||||||
|
|
||||||
class EventServiceProvider extends ServiceProvider
|
class EventServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
protected $listen = [];
|
protected $listen = [
|
||||||
|
VacationRequestStateChanged::class => [CreateVacationRequestActivity::class],
|
||||||
|
VacationRequestCreated::class => [HandleCreatedVacationRequest::class],
|
||||||
|
VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class],
|
||||||
|
VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class],
|
||||||
|
VacationRequestApproved::class => [HandleApprovedVacationRequest::class],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,10 @@ namespace Toby\Architecture\Providers;
|
|||||||
|
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
use Toby\Eloquent\Models\YearPeriod;
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
use Toby\Eloquent\Observers\UserObserver;
|
use Toby\Eloquent\Observers\UserObserver;
|
||||||
|
use Toby\Eloquent\Observers\VacationRequestObserver;
|
||||||
use Toby\Eloquent\Observers\YearPeriodObserver;
|
use Toby\Eloquent\Observers\YearPeriodObserver;
|
||||||
|
|
||||||
class ObserverServiceProvider extends ServiceProvider
|
class ObserverServiceProvider extends ServiceProvider
|
||||||
@ -16,5 +18,6 @@ class ObserverServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
User::observe(UserObserver::class);
|
User::observe(UserObserver::class);
|
||||||
YearPeriod::observe(YearPeriodObserver::class);
|
YearPeriod::observe(YearPeriodObserver::class);
|
||||||
|
VacationRequest::observe(VacationRequestObserver::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Toby\Domain;
|
namespace Toby\Domain\Enums;
|
||||||
|
|
||||||
enum EmploymentForm: string
|
enum EmploymentForm: string
|
||||||
{
|
{
|
30
app/Domain/Enums/Role.php
Normal file
30
app/Domain/Enums/Role.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Enums;
|
||||||
|
|
||||||
|
enum Role: string
|
||||||
|
{
|
||||||
|
case EMPLOYEE = "employee";
|
||||||
|
case ADMINISTRATOR = "administrator";
|
||||||
|
case TECHNICAL_APPROVER = "technical_approver";
|
||||||
|
case ADMINISTRATIVE_APPROVER = "administrative_approver";
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return __($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function casesToSelect(): array
|
||||||
|
{
|
||||||
|
$cases = collect(Role::cases());
|
||||||
|
|
||||||
|
return $cases->map(
|
||||||
|
fn(Role $enum) => [
|
||||||
|
"label" => $enum->label(),
|
||||||
|
"value" => $enum->value,
|
||||||
|
],
|
||||||
|
)->toArray();
|
||||||
|
}
|
||||||
|
}
|
56
app/Domain/Enums/VacationRequestState.php
Normal file
56
app/Domain/Enums/VacationRequestState.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Enums;
|
||||||
|
|
||||||
|
enum VacationRequestState: string
|
||||||
|
{
|
||||||
|
case CREATED = "created";
|
||||||
|
case CANCELED = "canceled";
|
||||||
|
case REJECTED = "rejected";
|
||||||
|
case APPROVED = "approved";
|
||||||
|
case WAITING_FOR_TECHNICAL = "waiting_for_technical";
|
||||||
|
case WAITING_FOR_ADMINISTRATIVE = "waiting_for_administrative";
|
||||||
|
case ACCEPTED_BY_TECHNICAL = "accepted_by_technical";
|
||||||
|
case ACCEPTED_BY_ADMINSTRATIVE = "accepted_by_administrative";
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return __($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function pendingStates(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::CREATED,
|
||||||
|
self::WAITING_FOR_TECHNICAL,
|
||||||
|
self::WAITING_FOR_ADMINISTRATIVE,
|
||||||
|
self::ACCEPTED_BY_TECHNICAL,
|
||||||
|
self::ACCEPTED_BY_ADMINSTRATIVE,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function successStates(): array
|
||||||
|
{
|
||||||
|
return [self::APPROVED];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function failedStates(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
self::REJECTED,
|
||||||
|
self::CANCELED,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function filterByStatus(string $filter): array
|
||||||
|
{
|
||||||
|
return match ($filter) {
|
||||||
|
"pending" => VacationRequestState::pendingStates(),
|
||||||
|
"success" => VacationRequestState::successStates(),
|
||||||
|
"failed" => VacationRequestState::failedStates(),
|
||||||
|
default => VacationRequestState::cases(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
35
app/Domain/Enums/VacationType.php
Normal file
35
app/Domain/Enums/VacationType.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Enums;
|
||||||
|
|
||||||
|
enum VacationType: string
|
||||||
|
{
|
||||||
|
case VACATION = "vacation";
|
||||||
|
case VACATION_ON_REQUEST = "vacation_on_request";
|
||||||
|
case SPECIAL_VACATION = "special_vacation";
|
||||||
|
case CHILDCARE_VACATION = "childcare_vacation";
|
||||||
|
case TRAINING_VACATION = "training_vacation";
|
||||||
|
case UNPAID_VACATION = "unpaid_vacation";
|
||||||
|
case VOLUNTEERING_VACATION = "volunteering_vacation";
|
||||||
|
case TIME_IN_LIEU = "time_in_lieu";
|
||||||
|
case SICK_VACATION = "sick_vacation";
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return __($this->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function casesToSelect(): array
|
||||||
|
{
|
||||||
|
$cases = collect(VacationType::cases());
|
||||||
|
|
||||||
|
return $cases->map(
|
||||||
|
fn(VacationType $enum) => [
|
||||||
|
"label" => $enum->label(),
|
||||||
|
"value" => $enum->value,
|
||||||
|
],
|
||||||
|
)->toArray();
|
||||||
|
}
|
||||||
|
}
|
@ -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 VacationRequestAcceptedByAdministrative
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public VacationRequest $vacationRequest,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestAcceptedByTechnical.php
Normal file
20
app/Domain/Events/VacationRequestAcceptedByTechnical.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 VacationRequestAcceptedByTechnical
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public VacationRequest $vacationRequest,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestApproved.php
Normal file
20
app/Domain/Events/VacationRequestApproved.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 VacationRequestApproved
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public VacationRequest $vacationRequest,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
20
app/Domain/Events/VacationRequestCreated.php
Normal file
20
app/Domain/Events/VacationRequestCreated.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 VacationRequestCreated
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public VacationRequest $vacationRequest,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
25
app/Domain/Events/VacationRequestStateChanged.php
Normal file
25
app/Domain/Events/VacationRequestStateChanged.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Events;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Events\Dispatchable;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class VacationRequestStateChanged
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public VacationRequest $vacationRequest,
|
||||||
|
public ?VacationRequestState $from,
|
||||||
|
public VacationRequestState $to,
|
||||||
|
public ?User $user = null,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
19
app/Domain/Listeners/CreateVacationRequestActivity.php
Normal file
19
app/Domain/Listeners/CreateVacationRequestActivity.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Listeners;
|
||||||
|
|
||||||
|
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||||
|
|
||||||
|
class CreateVacationRequestActivity
|
||||||
|
{
|
||||||
|
public function handle(VacationRequestStateChanged $event): void
|
||||||
|
{
|
||||||
|
$event->vacationRequest->activities()->create([
|
||||||
|
"from" => $event->from,
|
||||||
|
"to" => $event->to,
|
||||||
|
"user_id" => $event->user?->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Listeners;
|
||||||
|
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
|
||||||
|
class HandleAcceptedByAdministrativeVacationRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected VacationRequestStateManager $stateManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(VacationRequestAcceptedByAdministrative $event): void
|
||||||
|
{
|
||||||
|
$this->stateManager->approve($event->vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Listeners;
|
||||||
|
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
|
||||||
|
class HandleAcceptedByTechnicalVacationRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected VacationTypeConfigRetriever $configRetriever,
|
||||||
|
protected VacationRequestStateManager $stateManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(VacationRequestAcceptedByTechnical $event): void
|
||||||
|
{
|
||||||
|
$vacationRequest = $event->vacationRequest;
|
||||||
|
|
||||||
|
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
|
||||||
|
$this->stateManager->waitForAdministrative($vacationRequest);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stateManager->approve($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
24
app/Domain/Listeners/HandleApprovedVacationRequest.php
Normal file
24
app/Domain/Listeners/HandleApprovedVacationRequest.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Listeners;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Toby\Domain\Events\VacationRequestApproved;
|
||||||
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
|
||||||
|
class HandleApprovedVacationRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected VacationTypeConfigRetriever $configRetriever,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(VacationRequestApproved $event): void
|
||||||
|
{
|
||||||
|
$vacationRequest = $event->vacationRequest;
|
||||||
|
|
||||||
|
Log::info("approved! {$vacationRequest->id}");
|
||||||
|
}
|
||||||
|
}
|
37
app/Domain/Listeners/HandleCreatedVacationRequest.php
Normal file
37
app/Domain/Listeners/HandleCreatedVacationRequest.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Listeners;
|
||||||
|
|
||||||
|
use Toby\Domain\Events\VacationRequestCreated;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
|
||||||
|
class HandleCreatedVacationRequest
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected VacationTypeConfigRetriever $configRetriever,
|
||||||
|
protected VacationRequestStateManager $stateManager,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(VacationRequestCreated $event): void
|
||||||
|
{
|
||||||
|
$vacationRequest = $event->vacationRequest;
|
||||||
|
|
||||||
|
if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) {
|
||||||
|
$this->stateManager->waitForTechnical($vacationRequest);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) {
|
||||||
|
$this->stateManager->waitForAdministrative($vacationRequest);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stateManager->approve($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
76
app/Domain/VacationRequestStateManager.php
Normal file
76
app/Domain/VacationRequestStateManager.php
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByAdministrative;
|
||||||
|
use Toby\Domain\Events\VacationRequestAcceptedByTechnical;
|
||||||
|
use Toby\Domain\Events\VacationRequestApproved;
|
||||||
|
use Toby\Domain\Events\VacationRequestCreated;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class VacationRequestStateManager
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Auth $auth,
|
||||||
|
protected Dispatcher $dispatcher,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function markAsCreated(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::CREATED);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function approve(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::APPROVED);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::REJECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::CANCELED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acceptAsTechnical(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_TECHNICAL);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acceptAsAdministrative(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_ADMINSTRATIVE);
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function waitForTechnical(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_TECHNICAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function waitForAdministrative(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_ADMINISTRATIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void
|
||||||
|
{
|
||||||
|
$vacationRequest->changeStateTo($state);
|
||||||
|
}
|
||||||
|
}
|
46
app/Domain/VacationTypeConfigRetriever.php
Normal file
46
app/Domain/VacationTypeConfigRetriever.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Config\Repository;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
|
||||||
|
class VacationTypeConfigRetriever
|
||||||
|
{
|
||||||
|
public const KEY_TECHNICAL_APPROVAL = "technical_approval";
|
||||||
|
public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval";
|
||||||
|
public const KEY_BILLABLE = "billable";
|
||||||
|
public const KEY_HAS_LIMIT = "has_limit";
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected Repository $config,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function needsTechnicalApproval(VacationType $type): bool
|
||||||
|
{
|
||||||
|
return $this->getConfigFor($type)[static::KEY_TECHNICAL_APPROVAL];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function needsAdministrativeApproval(VacationType $type): bool
|
||||||
|
{
|
||||||
|
return $this->getConfigFor($type)[static::KEY_ADMINISTRATIVE_APPROVAL];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isBillable(VacationType $type): bool
|
||||||
|
{
|
||||||
|
return $this->getConfigFor($type)[static::KEY_BILLABLE];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasLimit(VacationType $type): bool
|
||||||
|
{
|
||||||
|
return $this->getConfigFor($type)[static::KEY_HAS_LIMIT];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getConfigFor(VacationType $type): array
|
||||||
|
{
|
||||||
|
return $this->config->get("vacation_types.{$type->value}");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class ApprovedVacationDaysInSameRange implements VacationRequestRule
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||||
|
{
|
||||||
|
return $next($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
16
app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
Normal file
16
app/Domain/Validation/Rules/DoesNotExceedLimitRule.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class DoesNotExceedLimitRule implements VacationRequestRule
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||||
|
{
|
||||||
|
return $next($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
16
app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
Normal file
16
app/Domain/Validation/Rules/MinimumOneVacationDayRule.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class MinimumOneVacationDayRule implements VacationRequestRule
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||||
|
{
|
||||||
|
return $next($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class PendingVacationRequestInSameRange implements VacationRequestRule
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||||
|
{
|
||||||
|
return $next($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
16
app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
Normal file
16
app/Domain/Validation/Rules/UsedVacationDaysInSameRange.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class UsedVacationDaysInSameRange
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next)
|
||||||
|
{
|
||||||
|
return $next($vacationRequest);
|
||||||
|
}
|
||||||
|
}
|
13
app/Domain/Validation/Rules/VacationRequestRule.php
Normal file
13
app/Domain/Validation/Rules/VacationRequestRule.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation\Rules;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
interface VacationRequestRule
|
||||||
|
{
|
||||||
|
public function check(VacationRequest $vacationRequest, Closure $next);
|
||||||
|
}
|
35
app/Domain/Validation/VacationRequestValidator.php
Normal file
35
app/Domain/Validation/VacationRequestValidator.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Domain\Validation;
|
||||||
|
|
||||||
|
use Illuminate\Pipeline\Pipeline;
|
||||||
|
use Toby\Domain\Validation\Rules\ApprovedVacationDaysInSameRange;
|
||||||
|
use Toby\Domain\Validation\Rules\DoesNotExceedLimitRule;
|
||||||
|
use Toby\Domain\Validation\Rules\MinimumOneVacationDayRule;
|
||||||
|
use Toby\Domain\Validation\Rules\PendingVacationRequestInSameRange;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class VacationRequestValidator
|
||||||
|
{
|
||||||
|
protected array $rules = [
|
||||||
|
MinimumOneVacationDayRule::class,
|
||||||
|
DoesNotExceedLimitRule::class,
|
||||||
|
PendingVacationRequestInSameRange::class,
|
||||||
|
ApprovedVacationDaysInSameRange::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected Pipeline $pipeline,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$this->pipeline
|
||||||
|
->send($vacationRequest)
|
||||||
|
->through($this->rules)
|
||||||
|
->via("check");
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
|||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Toby\Domain\EmploymentForm;
|
use Toby\Domain\Enums\EmploymentForm;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
@ -21,9 +22,11 @@ use Toby\Domain\EmploymentForm;
|
|||||||
* @property string $last_name
|
* @property string $last_name
|
||||||
* @property string $email
|
* @property string $email
|
||||||
* @property string $avatar
|
* @property string $avatar
|
||||||
|
* @property Role $role
|
||||||
* @property EmploymentForm $employment_form
|
* @property EmploymentForm $employment_form
|
||||||
* @property Carbon $employment_date
|
* @property Carbon $employment_date
|
||||||
* @property Collection $vacationLimits
|
* @property Collection $vacationLimits
|
||||||
|
* @property Collection $vacationRequests
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
@ -34,6 +37,7 @@ class User extends Authenticatable
|
|||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
protected $casts = [
|
protected $casts = [
|
||||||
|
"role" => Role::class,
|
||||||
"employment_form" => EmploymentForm::class,
|
"employment_form" => EmploymentForm::class,
|
||||||
"employment_date" => "date",
|
"employment_date" => "date",
|
||||||
];
|
];
|
||||||
@ -47,6 +51,11 @@ class User extends Authenticatable
|
|||||||
return $this->hasMany(VacationLimit::class);
|
return $this->hasMany(VacationLimit::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function vacationRequests(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(VacationRequest::class);
|
||||||
|
}
|
||||||
|
|
||||||
public function scopeSearch(Builder $query, ?string $text): Builder
|
public function scopeSearch(Builder $query, ?string $text): Builder
|
||||||
{
|
{
|
||||||
if ($text === null) {
|
if ($text === null) {
|
||||||
@ -71,6 +80,11 @@ class User extends Authenticatable
|
|||||||
return "{$this->first_name} {$this->last_name}";
|
return "{$this->first_name} {$this->last_name}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function hasRole(Role $role): bool
|
||||||
|
{
|
||||||
|
return $this->role === $role;
|
||||||
|
}
|
||||||
|
|
||||||
protected static function newFactory(): UserFactory
|
protected static function newFactory(): UserFactory
|
||||||
{
|
{
|
||||||
return UserFactory::new();
|
return UserFactory::new();
|
||||||
|
67
app/Eloquent/Models/VacationRequest.php
Normal file
67
app/Eloquent/Models/VacationRequest.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Eloquent\Models;
|
||||||
|
|
||||||
|
use Database\Factories\VacationRequestFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property VacationType $type
|
||||||
|
* @property VacationRequestState $state
|
||||||
|
* @property Carbon $from
|
||||||
|
* @property Carbon $to
|
||||||
|
* @property string $comment
|
||||||
|
* @property User $user
|
||||||
|
* @property Collection $activities
|
||||||
|
*/
|
||||||
|
class VacationRequest extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
"type" => VacationType::class,
|
||||||
|
"state" => VacationRequestState::class,
|
||||||
|
"from" => "date",
|
||||||
|
"to" => "date",
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activities(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(VacationRequestActivity::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changeStateTo(VacationRequestState $state): void
|
||||||
|
{
|
||||||
|
$this->state = $state;
|
||||||
|
|
||||||
|
$this->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeStates(Builder $query, array $states): Builder
|
||||||
|
{
|
||||||
|
return $query->whereIn("state", $states);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function newFactory(): VacationRequestFactory
|
||||||
|
{
|
||||||
|
return VacationRequestFactory::new();
|
||||||
|
}
|
||||||
|
}
|
36
app/Eloquent/Models/VacationRequestActivity.php
Normal file
36
app/Eloquent/Models/VacationRequestActivity.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Eloquent\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $id
|
||||||
|
* @property VacationRequest $vacationRequest
|
||||||
|
* @property ?User $user
|
||||||
|
* @property ?VacationRequestState $from
|
||||||
|
* @property VacationRequestState $to
|
||||||
|
*/
|
||||||
|
class VacationRequestActivity extends Model
|
||||||
|
{
|
||||||
|
protected $guarded = [];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
"from" => VacationRequestState::class,
|
||||||
|
"to" => VacationRequestState::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function vacationRequest(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(VacationRequest::class);
|
||||||
|
}
|
||||||
|
}
|
59
app/Eloquent/Observers/VacationRequestObserver.php
Normal file
59
app/Eloquent/Observers/VacationRequestObserver.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Eloquent\Observers;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||||
|
use Illuminate\Events\Dispatcher;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Events\VacationRequestStateChanged;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class VacationRequestObserver
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected Auth $auth,
|
||||||
|
protected Dispatcher $dispatcher,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function creating(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
$year = $vacationRequest->from->year;
|
||||||
|
|
||||||
|
$vacationRequestNumber = $vacationRequest->user->vacationRequests()
|
||||||
|
->whereYear("from", $year)
|
||||||
|
->count() + 1;
|
||||||
|
|
||||||
|
$vacationRequest->name = "{$vacationRequestNumber}/${year}";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saved(VacationRequest $vacationRequest): void
|
||||||
|
{
|
||||||
|
if ($vacationRequest->isDirty("state")) {
|
||||||
|
$previousState = $vacationRequest->getOriginal("state");
|
||||||
|
|
||||||
|
$this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fireStateChangedEvent(
|
||||||
|
VacationRequest $vacationRequest,
|
||||||
|
?VacationRequestState $from,
|
||||||
|
VacationRequestState $to,
|
||||||
|
): void {
|
||||||
|
$event = new VacationRequestStateChanged($vacationRequest, $from, $to, $this->getAuthUser());
|
||||||
|
|
||||||
|
$this->dispatcher->dispatch($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getAuthUser(): ?User
|
||||||
|
{
|
||||||
|
/** @var User $user */
|
||||||
|
$user = $this->auth->guard()->user();
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Controllers;
|
|||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
use Toby\Domain\EmploymentForm;
|
use Toby\Domain\Enums\EmploymentForm;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
use Toby\Infrastructure\Http\Requests\UserRequest;
|
use Toby\Infrastructure\Http\Requests\UserRequest;
|
||||||
use Toby\Infrastructure\Http\Resources\UserFormDataResource;
|
use Toby\Infrastructure\Http\Resources\UserFormDataResource;
|
||||||
@ -35,6 +36,7 @@ class UserController extends Controller
|
|||||||
{
|
{
|
||||||
return inertia("Users/Create", [
|
return inertia("Users/Create", [
|
||||||
"employmentForms" => EmploymentForm::casesToSelect(),
|
"employmentForms" => EmploymentForm::casesToSelect(),
|
||||||
|
"roles" => Role::casesToSelect(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,6 +54,7 @@ class UserController extends Controller
|
|||||||
return inertia("Users/Edit", [
|
return inertia("Users/Edit", [
|
||||||
"user" => new UserFormDataResource($user),
|
"user" => new UserFormDataResource($user),
|
||||||
"employmentForms" => EmploymentForm::casesToSelect(),
|
"employmentForms" => EmploymentForm::casesToSelect(),
|
||||||
|
"roles" => Role::casesToSelect(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Controllers;
|
||||||
|
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Response;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\VacationRequestStateManager;
|
||||||
|
use Toby\Domain\Validation\VacationRequestValidator;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
|
||||||
|
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
|
||||||
|
use Toby\Infrastructure\Http\Resources\VacationRequestResource;
|
||||||
|
|
||||||
|
class VacationRequestController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request): Response
|
||||||
|
{
|
||||||
|
$vacationRequests = $request->user()
|
||||||
|
->vacationRequests()
|
||||||
|
->latest()
|
||||||
|
->states(VacationRequestState::filterByStatus($request->query("status", "all")))
|
||||||
|
->paginate();
|
||||||
|
|
||||||
|
return inertia("VacationRequest/Index", [
|
||||||
|
"requests" => VacationRequestResource::collection($vacationRequests),
|
||||||
|
"filters" => $request->only("status"),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function show(VacationRequest $vacationRequest): Response
|
||||||
|
{
|
||||||
|
return inertia("VacationRequest/Show", [
|
||||||
|
"request" => new VacationRequestResource($vacationRequest),
|
||||||
|
"activities" => VacationRequestActivityResource::collection($vacationRequest->activities),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(): Response
|
||||||
|
{
|
||||||
|
return inertia("VacationRequest/Create", [
|
||||||
|
"vacationTypes" => VacationType::casesToSelect(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(
|
||||||
|
VacationRequestRequest $request,
|
||||||
|
VacationRequestValidator $vacationRequestValidator,
|
||||||
|
VacationRequestStateManager $stateManager,
|
||||||
|
): RedirectResponse {
|
||||||
|
/** @var VacationRequest $vacationRequest */
|
||||||
|
$vacationRequest = $request->user()->vacationRequests()->make($request->data());
|
||||||
|
|
||||||
|
$vacationRequestValidator->validate($vacationRequest);
|
||||||
|
|
||||||
|
$vacationRequest->save();
|
||||||
|
$stateManager->markAsCreated($vacationRequest);
|
||||||
|
|
||||||
|
return redirect()
|
||||||
|
->route("vacation.requests.index");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reject(
|
||||||
|
VacationRequest $vacationRequest,
|
||||||
|
VacationRequestStateManager $stateManager,
|
||||||
|
): RedirectResponse {
|
||||||
|
$stateManager->reject($vacationRequest);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(
|
||||||
|
VacationRequest $vacationRequest,
|
||||||
|
VacationRequestStateManager $stateManager,
|
||||||
|
): RedirectResponse {
|
||||||
|
$stateManager->cancel($vacationRequest);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acceptAsTechnical(
|
||||||
|
VacationRequest $vacationRequest,
|
||||||
|
VacationRequestStateManager $stateManager,
|
||||||
|
): RedirectResponse {
|
||||||
|
$stateManager->acceptAsTechnical($vacationRequest);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acceptAsAdministrative(
|
||||||
|
VacationRequest $vacationRequest,
|
||||||
|
VacationRequestStateManager $stateManager,
|
||||||
|
): RedirectResponse {
|
||||||
|
$stateManager->acceptAsAdministrative($vacationRequest);
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ use Illuminate\Routing\Middleware\ValidateSignature;
|
|||||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||||
use Illuminate\Session\Middleware\StartSession;
|
use Illuminate\Session\Middleware\StartSession;
|
||||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
|
||||||
use Toby\Infrastructure\Http\Middleware\Authenticate;
|
use Toby\Infrastructure\Http\Middleware\Authenticate;
|
||||||
use Toby\Infrastructure\Http\Middleware\HandleInertiaRequests;
|
use Toby\Infrastructure\Http\Middleware\HandleInertiaRequests;
|
||||||
use Toby\Infrastructure\Http\Middleware\RedirectIfAuthenticated;
|
use Toby\Infrastructure\Http\Middleware\RedirectIfAuthenticated;
|
||||||
@ -52,6 +53,7 @@ class Kernel extends HttpKernel
|
|||||||
HandleInertiaRequests::class,
|
HandleInertiaRequests::class,
|
||||||
],
|
],
|
||||||
"api" => [
|
"api" => [
|
||||||
|
EnsureFrontendRequestsAreStateful::class,
|
||||||
"throttle:api",
|
"throttle:api",
|
||||||
SubstituteBindings::class,
|
SubstituteBindings::class,
|
||||||
],
|
],
|
||||||
|
@ -7,7 +7,8 @@ namespace Toby\Infrastructure\Http\Requests;
|
|||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
use Illuminate\Validation\Rules\Enum;
|
use Illuminate\Validation\Rules\Enum;
|
||||||
use Toby\Domain\EmploymentForm;
|
use Toby\Domain\Enums\EmploymentForm;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
|
|
||||||
class UserRequest extends FormRequest
|
class UserRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@ -17,6 +18,7 @@ class UserRequest extends FormRequest
|
|||||||
"firstName" => ["required", "min:3", "max:80"],
|
"firstName" => ["required", "min:3", "max:80"],
|
||||||
"lastName" => ["required", "min:3", "max:80"],
|
"lastName" => ["required", "min:3", "max:80"],
|
||||||
"email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)],
|
"email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)],
|
||||||
|
"role" => ["required", new Enum(Role::class)],
|
||||||
"employmentForm" => ["required", new Enum(EmploymentForm::class)],
|
"employmentForm" => ["required", new Enum(EmploymentForm::class)],
|
||||||
"employmentDate" => ["required", "date_format:Y-m-d"],
|
"employmentDate" => ["required", "date_format:Y-m-d"],
|
||||||
];
|
];
|
||||||
@ -28,6 +30,7 @@ class UserRequest extends FormRequest
|
|||||||
"first_name" => $this->get("firstName"),
|
"first_name" => $this->get("firstName"),
|
||||||
"last_name" => $this->get("lastName"),
|
"last_name" => $this->get("lastName"),
|
||||||
"email" => $this->get("email"),
|
"email" => $this->get("email"),
|
||||||
|
"role" => $this->get("role"),
|
||||||
"employment_form" => $this->get("employmentForm"),
|
"employment_form" => $this->get("employmentForm"),
|
||||||
"employment_date" => $this->get("employmentDate"),
|
"employment_date" => $this->get("employmentDate"),
|
||||||
];
|
];
|
||||||
|
33
app/Infrastructure/Http/Requests/VacationRequestRequest.php
Normal file
33
app/Infrastructure/Http/Requests/VacationRequestRequest.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
use Illuminate\Validation\Rules\Enum;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Infrastructure\Http\Rules\YearPeriodExists;
|
||||||
|
|
||||||
|
class VacationRequestRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"type" => ["required", new Enum(VacationType::class)],
|
||||||
|
"from" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||||
|
"to" => ["required", "date_format:Y-m-d", new YearPeriodExists()],
|
||||||
|
"comment" => ["nullable"],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function data(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"type" => $this->get("type"),
|
||||||
|
"from" => $this->get("from"),
|
||||||
|
"to" => $this->get("to"),
|
||||||
|
"comment" => $this->get("comment"),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ class HolidayResource extends JsonResource
|
|||||||
return [
|
return [
|
||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
"name" => $this->name,
|
"name" => $this->name,
|
||||||
|
"date" => $this->date->toDateString(),
|
||||||
"displayDate" => $this->date->toDisplayString(),
|
"displayDate" => $this->date->toDisplayString(),
|
||||||
"dayOfWeek" => $this->date->dayName,
|
"dayOfWeek" => $this->date->dayName,
|
||||||
];
|
];
|
||||||
|
@ -17,8 +17,9 @@ class UserFormDataResource extends JsonResource
|
|||||||
"firstName" => $this->first_name,
|
"firstName" => $this->first_name,
|
||||||
"lastName" => $this->last_name,
|
"lastName" => $this->last_name,
|
||||||
"email" => $this->email,
|
"email" => $this->email,
|
||||||
|
"role" => $this->role,
|
||||||
"employmentForm" => $this->employment_form,
|
"employmentForm" => $this->employment_form,
|
||||||
"employmentDate" => $this->employment_date,
|
"employmentDate" => $this->employment_date->toDateString(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ class UserResource extends JsonResource
|
|||||||
"id" => $this->id,
|
"id" => $this->id,
|
||||||
"name" => $this->fullName,
|
"name" => $this->fullName,
|
||||||
"email" => $this->email,
|
"email" => $this->email,
|
||||||
"role" => "Human Resources Manager",
|
"role" => $this->role->label(),
|
||||||
"avatar" => asset($this->avatar),
|
"avatar" => asset($this->avatar),
|
||||||
"deleted" => $this->trashed(),
|
"deleted" => $this->trashed(),
|
||||||
"employmentForm" => $this->employment_form->label(),
|
"employmentForm" => $this->employment_form->label(),
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class VacationRequestActivityResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"date" => $this->created_at->toDisplayString(),
|
||||||
|
"who" => $this->user ? $this->user->fullName : __("System"),
|
||||||
|
"to" => $this->to->label(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Toby\Infrastructure\Http\Resources;
|
||||||
|
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
|
||||||
|
class VacationRequestResource extends JsonResource
|
||||||
|
{
|
||||||
|
public static $wrap = null;
|
||||||
|
|
||||||
|
public function toArray($request): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"id" => $this->id,
|
||||||
|
"name" => $this->name,
|
||||||
|
"user" => new UserResource($this->user),
|
||||||
|
"type" => $this->type->label(),
|
||||||
|
"state" => $this->state->label(),
|
||||||
|
"from" => $this->from->toDisplayString(),
|
||||||
|
"to" => $this->to->toDisplayString(),
|
||||||
|
"comment" => $this->comment,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@
|
|||||||
"guzzlehttp/guzzle": "^7.0.1",
|
"guzzlehttp/guzzle": "^7.0.1",
|
||||||
"inertiajs/inertia-laravel": "^0.5.1",
|
"inertiajs/inertia-laravel": "^0.5.1",
|
||||||
"laravel/framework": "^8.75",
|
"laravel/framework": "^8.75",
|
||||||
|
"laravel/sanctum": "^2.14",
|
||||||
"laravel/socialite": "^5.2",
|
"laravel/socialite": "^5.2",
|
||||||
"laravel/telescope": "^4.6",
|
"laravel/telescope": "^4.6",
|
||||||
"laravel/tinker": "^2.5",
|
"laravel/tinker": "^2.5",
|
||||||
|
429
composer.lock
generated
429
composer.lock
generated
File diff suppressed because it is too large
Load Diff
20
config/sanctum.php
Normal file
20
config/sanctum.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"stateful" => explode(",", env("SANCTUM_STATEFUL_DOMAINS", sprintf(
|
||||||
|
"%s%s",
|
||||||
|
"localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1",
|
||||||
|
env("APP_URL") ? "," . parse_url(env("APP_URL"), PHP_URL_HOST) : "",
|
||||||
|
))),
|
||||||
|
"guard" => ["web"],
|
||||||
|
"expiration" => null,
|
||||||
|
"middleware" => [
|
||||||
|
"verify_csrf_token" => VerifyCsrfToken::class,
|
||||||
|
"encrypt_cookies" => EncryptCookies::class,
|
||||||
|
],
|
||||||
|
];
|
63
config/vacation_types.php
Normal file
63
config/vacation_types.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Domain\VacationTypeConfigRetriever;
|
||||||
|
|
||||||
|
return [
|
||||||
|
VacationType::VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
||||||
|
],
|
||||||
|
VacationType::VACATION_ON_REQUEST->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => true,
|
||||||
|
],
|
||||||
|
VacationType::TIME_IN_LIEU->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::SICK_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::UNPAID_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::SPECIAL_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::CHILDCARE_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => false,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::TRAINING_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
VacationType::VOLUNTEERING_VACATION->value => [
|
||||||
|
VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_BILLABLE => true,
|
||||||
|
VacationTypeConfigRetriever::KEY_HAS_LIMIT => false,
|
||||||
|
],
|
||||||
|
];
|
@ -7,7 +7,8 @@ namespace Database\Factories;
|
|||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Toby\Domain\EmploymentForm;
|
use Toby\Domain\Enums\EmploymentForm;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
|
|
||||||
class UserFactory extends Factory
|
class UserFactory extends Factory
|
||||||
@ -21,6 +22,7 @@ class UserFactory extends Factory
|
|||||||
"last_name" => $this->faker->lastName(),
|
"last_name" => $this->faker->lastName(),
|
||||||
"email" => $this->faker->unique()->safeEmail(),
|
"email" => $this->faker->unique()->safeEmail(),
|
||||||
"employment_form" => $this->faker->randomElement(EmploymentForm::cases()),
|
"employment_form" => $this->faker->randomElement(EmploymentForm::cases()),
|
||||||
|
"role" => Role::EMPLOYEE,
|
||||||
"employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(),
|
"employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(),
|
||||||
"remember_token" => Str::random(10),
|
"remember_token" => Str::random(10),
|
||||||
];
|
];
|
||||||
|
32
database/factories/VacationRequestFactory.php
Normal file
32
database/factories/VacationRequestFactory.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Toby\Domain\Enums\VacationRequestState;
|
||||||
|
use Toby\Domain\Enums\VacationType;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
class VacationRequestFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = VacationRequest::class;
|
||||||
|
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$number = $this->faker->numberBetween(1, 20);
|
||||||
|
$year = $this->faker->year;
|
||||||
|
|
||||||
|
return [
|
||||||
|
"user_id" => User::factory(),
|
||||||
|
"name" => "{$number}/{$year}",
|
||||||
|
"type" => $this->faker->randomElement(VacationType::cases()),
|
||||||
|
"state" => $this->faker->randomElement(VacationRequestState::cases()),
|
||||||
|
"from" => $this->faker->date,
|
||||||
|
"to" => $this->faker->date,
|
||||||
|
"comment" => $this->faker->boolean ? $this->faker->paragraph() : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
|
|
||||||
return new class() extends Migration {
|
return new class() extends Migration {
|
||||||
public function up(): void
|
public function up(): void
|
||||||
@ -15,6 +16,7 @@ return new class() extends Migration {
|
|||||||
$table->string("last_name");
|
$table->string("last_name");
|
||||||
$table->string("email")->unique();
|
$table->string("email")->unique();
|
||||||
$table->string("avatar")->nullable();
|
$table->string("avatar")->nullable();
|
||||||
|
$table->string("role")->default(Role::EMPLOYEE->value);
|
||||||
$table->string("employment_form");
|
$table->string("employment_form");
|
||||||
$table->date("employment_date");
|
$table->date("employment_date");
|
||||||
$table->rememberToken();
|
$table->rememberToken();
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
|
||||||
|
return new class() extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create("vacation_requests", function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->string("name");
|
||||||
|
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->string("type");
|
||||||
|
$table->string("state")->nullable();
|
||||||
|
$table->date("from");
|
||||||
|
$table->date("to");
|
||||||
|
$table->text("comment")->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists("vacation_requests");
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Toby\Eloquent\Models\User;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
|
|
||||||
|
return new class() extends Migration {
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create("vacation_request_activities", function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignIdFor(VacationRequest::class)->constrained()->cascadeOnDelete();
|
||||||
|
$table->foreignIdFor(User::class)->nullable()->constrained()->cascadeOnDelete();
|
||||||
|
$table->string("from")->nullable();
|
||||||
|
$table->string("to");
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists("vacation_request_activities");
|
||||||
|
}
|
||||||
|
};
|
@ -11,6 +11,7 @@ use Toby\Domain\PolishHolidaysRetriever;
|
|||||||
use Toby\Eloquent\Helpers\UserAvatarGenerator;
|
use Toby\Eloquent\Helpers\UserAvatarGenerator;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
use Toby\Eloquent\Models\VacationLimit;
|
use Toby\Eloquent\Models\VacationLimit;
|
||||||
|
use Toby\Eloquent\Models\VacationRequest;
|
||||||
use Toby\Eloquent\Models\YearPeriod;
|
use Toby\Eloquent\Models\YearPeriod;
|
||||||
|
|
||||||
class DatabaseSeeder extends Seeder
|
class DatabaseSeeder extends Seeder
|
||||||
@ -24,11 +25,14 @@ class DatabaseSeeder extends Seeder
|
|||||||
{
|
{
|
||||||
User::unsetEventDispatcher();
|
User::unsetEventDispatcher();
|
||||||
YearPeriod::unsetEventDispatcher();
|
YearPeriod::unsetEventDispatcher();
|
||||||
|
VacationRequest::unsetEventDispatcher();
|
||||||
|
|
||||||
User::factory(9)->create();
|
User::factory(9)->create();
|
||||||
User::factory([
|
User::factory([
|
||||||
"email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"),
|
"email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"),
|
||||||
])->create();
|
])
|
||||||
|
->hasVacationRequests(5)
|
||||||
|
->create();
|
||||||
|
|
||||||
$users = User::all();
|
$users = User::all();
|
||||||
|
|
||||||
|
2677
package-lock.json
generated
2677
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -22,12 +22,14 @@
|
|||||||
"@tailwindcss/typography": "^0.5.0",
|
"@tailwindcss/typography": "^0.5.0",
|
||||||
"@vue/compiler-sfc": "^3.2.26",
|
"@vue/compiler-sfc": "^3.2.26",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
|
"echarts": "^5.2.2",
|
||||||
"flatpickr": "^4.6.9",
|
"flatpickr": "^4.6.9",
|
||||||
"laravel-mix": "^6.0.6",
|
"laravel-mix": "^6.0.6",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"tailwindcss": "^3.0.13",
|
"tailwindcss": "^3.0.13",
|
||||||
"vue": "^3.2.26",
|
"vue": "^3.2.26",
|
||||||
|
"vue-echarts": "^6.0.2",
|
||||||
"vue-flatpickr-component": "^9.0.5",
|
"vue-flatpickr-component": "^9.0.5",
|
||||||
"vue-loader": "^17.0.0"
|
"vue-loader": "^17.0.0"
|
||||||
},
|
},
|
||||||
|
@ -47,3 +47,24 @@
|
|||||||
-webkit-box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
|
-webkit-box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
|
||||||
box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
|
box-shadow: -5px 0 0 #527ABA, 5px 0 0 #527ABA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
border-radius: 100vh;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #dadce0;
|
||||||
|
border: 4px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #dadce0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -81,8 +81,8 @@
|
|||||||
</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'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HolidayCreate',
|
name: 'HolidayCreate',
|
||||||
@ -93,14 +93,14 @@ export default {
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
name: null,
|
name: null,
|
||||||
date: null,
|
date: null,
|
||||||
});
|
})
|
||||||
|
|
||||||
return { form };
|
return { form }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createHoliday() {
|
createHoliday() {
|
||||||
this.form.post('/holidays');
|
this.form.post('/holidays')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -81,8 +81,8 @@
|
|||||||
</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'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HolidayEdit',
|
name: 'HolidayEdit',
|
||||||
@ -99,15 +99,15 @@ export default {
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
name: props.holiday.name,
|
name: props.holiday.name,
|
||||||
date: props.holiday.date,
|
date: props.holiday.date,
|
||||||
});
|
})
|
||||||
|
|
||||||
return { form };
|
return { form }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
editHoliday() {
|
editHoliday() {
|
||||||
this.form
|
this.form
|
||||||
.put(`/holidays/${this.holiday.id}`);
|
.put(`/holidays/${this.holiday.id}`)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -140,8 +140,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid';
|
import { DotsVerticalIcon, PencilIcon, TrashIcon } from '@heroicons/vue/solid'
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HolidayINdex',
|
name: 'HolidayINdex',
|
||||||
@ -161,7 +161,7 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {}
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -65,12 +65,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import GuestLayout from '@/Shared/Layout/GuestLayout';
|
import GuestLayout from '@/Shared/Layout/GuestLayout'
|
||||||
import {XIcon} from '@heroicons/vue/solid';
|
import {XIcon} from '@heroicons/vue/solid'
|
||||||
import {ExclamationIcon} from '@heroicons/vue/solid';
|
import {ExclamationIcon} from '@heroicons/vue/solid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Login',
|
name: 'LoginPage',
|
||||||
components: {
|
components: {
|
||||||
XIcon,
|
XIcon,
|
||||||
ExclamationIcon,
|
ExclamationIcon,
|
||||||
@ -82,5 +82,5 @@ export default {
|
|||||||
default: () => ({oauth: null}),
|
default: () => ({oauth: null}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -82,6 +82,60 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Listbox
|
||||||
|
v-model="form.role"
|
||||||
|
as="div"
|
||||||
|
class="sm:grid sm:grid-cols-3 py-4 items-center"
|
||||||
|
>
|
||||||
|
<ListboxLabel class="block text-sm font-medium text-gray-700">
|
||||||
|
Rola
|
||||||
|
</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.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }"
|
||||||
|
>
|
||||||
|
<span class="block truncate">{{ form.role.label }}</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="role in roles"
|
||||||
|
:key="role.value"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
as="template"
|
||||||
|
:value="role"
|
||||||
|
>
|
||||||
|
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']">
|
||||||
|
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
|
||||||
|
{{ role.label }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<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.role"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.role }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
<Listbox
|
<Listbox
|
||||||
v-model="form.employmentForm"
|
v-model="form.employmentForm"
|
||||||
as="div"
|
as="div"
|
||||||
@ -182,13 +236,13 @@
|
|||||||
</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 { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue';
|
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '@headlessui/vue'
|
||||||
import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid';
|
import { CheckIcon, SelectorIcon } from '@heroicons/vue/solid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
employmentDate: 'UserCreate',
|
name: 'UserCreate',
|
||||||
components: {
|
components: {
|
||||||
FlatPickr,
|
FlatPickr,
|
||||||
Listbox,
|
Listbox,
|
||||||
@ -204,6 +258,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => null,
|
default: () => null,
|
||||||
},
|
},
|
||||||
|
roles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@ -211,10 +269,11 @@ export default {
|
|||||||
lastName: null,
|
lastName: null,
|
||||||
email: null,
|
email: null,
|
||||||
employmentForm: props.employmentForms[0],
|
employmentForm: props.employmentForms[0],
|
||||||
|
role: props.roles[0],
|
||||||
employmentDate: null,
|
employmentDate: null,
|
||||||
});
|
})
|
||||||
|
|
||||||
return { form };
|
return { form }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
createUser() {
|
createUser() {
|
||||||
@ -222,9 +281,10 @@ export default {
|
|||||||
.transform(data => ({
|
.transform(data => ({
|
||||||
...data,
|
...data,
|
||||||
employmentForm: data.employmentForm.value,
|
employmentForm: data.employmentForm.value,
|
||||||
|
role: data.role.value,
|
||||||
}))
|
}))
|
||||||
.post('/users');
|
.post('/users')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -82,6 +82,60 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Listbox
|
||||||
|
v-model="form.role"
|
||||||
|
as="div"
|
||||||
|
class="sm:grid sm:grid-cols-3 py-4 items-center"
|
||||||
|
>
|
||||||
|
<ListboxLabel class="block text-sm font-medium text-gray-700">
|
||||||
|
Rola
|
||||||
|
</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.employmentForm, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.employmentForm }"
|
||||||
|
>
|
||||||
|
<span class="block truncate">{{ form.role.label }}</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="role in roles"
|
||||||
|
:key="role.value"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
as="template"
|
||||||
|
:value="role"
|
||||||
|
>
|
||||||
|
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']">
|
||||||
|
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
|
||||||
|
{{ role.label }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<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.role"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.role }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
<Listbox
|
<Listbox
|
||||||
v-model="form.employmentForm"
|
v-model="form.employmentForm"
|
||||||
as="div"
|
as="div"
|
||||||
@ -100,7 +154,6 @@
|
|||||||
<SelectorIcon class="h-5 w-5 text-gray-400" />
|
<SelectorIcon class="h-5 w-5 text-gray-400" />
|
||||||
</span>
|
</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
leave-active-class="transition ease-in duration-100"
|
leave-active-class="transition ease-in duration-100"
|
||||||
leave-from-class="opacity-100"
|
leave-from-class="opacity-100"
|
||||||
@ -182,13 +235,13 @@
|
|||||||
</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 {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue';
|
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue'
|
||||||
import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid';
|
import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
employmentDate: 'UserEdit',
|
name: 'UserEdit',
|
||||||
components: {
|
components: {
|
||||||
FlatPickr,
|
FlatPickr,
|
||||||
Listbox,
|
Listbox,
|
||||||
@ -204,6 +257,10 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => null,
|
default: () => null,
|
||||||
},
|
},
|
||||||
|
roles: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
user: {
|
user: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => null,
|
default: () => null,
|
||||||
@ -214,11 +271,12 @@ export default {
|
|||||||
firstName: props.user.firstName,
|
firstName: props.user.firstName,
|
||||||
lastName: props.user.lastName,
|
lastName: props.user.lastName,
|
||||||
email: props.user.email,
|
email: props.user.email,
|
||||||
|
role: props.roles.find(role => role.value === props.user.role),
|
||||||
employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
|
employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
|
||||||
employmentDate: props.user.employmentDate,
|
employmentDate: props.user.employmentDate,
|
||||||
});
|
})
|
||||||
|
|
||||||
return { form };
|
return { form }
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
editUser() {
|
editUser() {
|
||||||
@ -226,9 +284,10 @@ export default {
|
|||||||
.transform(data => ({
|
.transform(data => ({
|
||||||
...data,
|
...data,
|
||||||
employmentForm: data.employmentForm.value,
|
employmentForm: data.employmentForm.value,
|
||||||
|
role: data.role.value,
|
||||||
}))
|
}))
|
||||||
.put(`/users/${this.user.id}`);
|
.put(`/users/${this.user.id}`)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -252,12 +252,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { ref, watch } from 'vue';
|
import { ref, watch } from 'vue'
|
||||||
import { Inertia } from '@inertiajs/inertia';
|
import { Inertia } from '@inertiajs/inertia'
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash'
|
||||||
import { SearchIcon } from '@heroicons/vue/outline';
|
import { SearchIcon } from '@heroicons/vue/outline'
|
||||||
import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid';
|
import { DotsVerticalIcon, PencilIcon, TrashIcon, RefreshIcon } from '@heroicons/vue/solid'
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'UserIndex',
|
name: 'UserIndex',
|
||||||
@ -283,18 +283,18 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
let search = ref(props.filters.search);
|
let search = ref(props.filters.search)
|
||||||
|
|
||||||
watch(search, debounce(value => {
|
watch(search, debounce(value => {
|
||||||
Inertia.get('/users', value ? { search: value} : {}, {
|
Inertia.get('/users', value ? { search: value} : {}, {
|
||||||
preserveState: true,
|
preserveState: true,
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
})
|
||||||
}, 300));
|
}, 300))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
search,
|
search,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -129,8 +129,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {Switch} from '@headlessui/vue';
|
import {Switch} from '@headlessui/vue'
|
||||||
import {useForm} from '@inertiajs/inertia-vue3';
|
import {useForm} from '@inertiajs/inertia-vue3'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VacationLimits',
|
name: 'VacationLimits',
|
||||||
@ -150,11 +150,11 @@ export default {
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
items: props.limits.data,
|
items: props.limits.data,
|
||||||
});
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
form,
|
form,
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submitVacationDays() {
|
submitVacationDays() {
|
||||||
@ -168,8 +168,8 @@ export default {
|
|||||||
.put('/vacation-limits', {
|
.put('/vacation-limits', {
|
||||||
preserveState: (page) => Object.keys(page.props.errors).length,
|
preserveState: (page) => Object.keys(page.props.errors).length,
|
||||||
preserveScroll: true,
|
preserveScroll: true,
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
240
resources/js/Pages/VacationRequest/Create.vue
Normal file
240
resources/js/Pages/VacationRequest/Create.vue
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
<template>
|
||||||
|
<InertiaHead title="Złóż wniosek urlopowy" />
|
||||||
|
<div class="bg-white sm:rounded-lg shadow-md">
|
||||||
|
<div class="p-4 sm:px-6">
|
||||||
|
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Złóż wniosek urlopowy
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
class="border-t border-gray-200 px-6"
|
||||||
|
@submit.prevent="createForm"
|
||||||
|
>
|
||||||
|
<Listbox
|
||||||
|
v-model="form.vacationType"
|
||||||
|
as="div"
|
||||||
|
class="sm:grid sm:grid-cols-3 py-4 items-center"
|
||||||
|
>
|
||||||
|
<ListboxLabel class="block text-sm font-medium text-gray-700">
|
||||||
|
Rodzaj wniosku
|
||||||
|
</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.vacationType, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.vacationType }"
|
||||||
|
>
|
||||||
|
<span class="block truncate">{{ form.vacationType.label }}</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="vacationType in vacationTypes"
|
||||||
|
:key="vacationType.value"
|
||||||
|
v-slot="{ active, selected }"
|
||||||
|
as="template"
|
||||||
|
:value="vacationType"
|
||||||
|
>
|
||||||
|
<li :class="[active ? 'text-white bg-blumilk-600' : 'text-gray-900', 'cursor-default select-none relative py-2 pl-3 pr-9']">
|
||||||
|
<span :class="[selected ? 'font-semibold' : 'font-normal', 'block truncate']">
|
||||||
|
{{ vacationType.label }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<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.vacationType"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.vacationType }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Listbox>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
|
||||||
|
<label
|
||||||
|
for="date_from"
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px"
|
||||||
|
>
|
||||||
|
Planowany urlop od
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<FlatPickr
|
||||||
|
id="date_from"
|
||||||
|
v-model="form.dateFrom"
|
||||||
|
:config="fromInputConfig"
|
||||||
|
placeholder="Wybierz datę"
|
||||||
|
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.dateFrom, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateFrom }"
|
||||||
|
@on-change="onFromChange"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
v-if="form.errors.dateFrom"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.dateFrom }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
|
||||||
|
<label
|
||||||
|
for="date_from"
|
||||||
|
class="block text-sm font-medium text-gray-700 sm:mt-px"
|
||||||
|
>
|
||||||
|
Planowany urlop do
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<FlatPickr
|
||||||
|
id="date_to"
|
||||||
|
v-model="form.dateTo"
|
||||||
|
:config="toInputConfig"
|
||||||
|
placeholder="Wybierz datę"
|
||||||
|
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.dateTo, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.dateTo }"
|
||||||
|
@on-change="onToChange"
|
||||||
|
/>
|
||||||
|
<p
|
||||||
|
v-if="form.errors.dateTo"
|
||||||
|
class="mt-2 text-sm text-red-600"
|
||||||
|
>
|
||||||
|
{{ form.errors.dateTo }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
|
||||||
|
<span class="block text-sm font-medium text-gray-700 sm:mt-px">Liczba dni urlopu</span>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2 w-full max-w-lg bg-gray-50 border border-gray-300 rounded-md px-4 py-2 inline-flex items-center text-gray-500 sm:text-sm">
|
||||||
|
1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
|
||||||
|
<label
|
||||||
|
for="comment"
|
||||||
|
class="block text-sm font-medium text-gray-700"
|
||||||
|
>
|
||||||
|
Komentarz
|
||||||
|
</label>
|
||||||
|
<div class="mt-1 sm:mt-0 sm:col-span-2">
|
||||||
|
<textarea
|
||||||
|
id="comment"
|
||||||
|
v-model="form.comment"
|
||||||
|
rows="4"
|
||||||
|
class="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full max-w-lg sm:text-sm border-gray-300 rounded-md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end py-3">
|
||||||
|
<div class="space-x-3">
|
||||||
|
<InertiaLink
|
||||||
|
href="/vacation-requests"
|
||||||
|
class="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm 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"
|
||||||
|
>
|
||||||
|
Anuluj
|
||||||
|
</InertiaLink>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
:disabled="form.processing"
|
||||||
|
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
|
>
|
||||||
|
Zapisz
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {useForm} from '@inertiajs/inertia-vue3'
|
||||||
|
import FlatPickr from 'vue-flatpickr-component'
|
||||||
|
import {Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions} from '@headlessui/vue'
|
||||||
|
import {CheckIcon, SelectorIcon} from '@heroicons/vue/solid'
|
||||||
|
import {reactive} from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationRequestCreate',
|
||||||
|
components: {
|
||||||
|
FlatPickr,
|
||||||
|
Listbox,
|
||||||
|
ListboxButton,
|
||||||
|
ListboxLabel,
|
||||||
|
ListboxOption,
|
||||||
|
ListboxOptions,
|
||||||
|
CheckIcon,
|
||||||
|
SelectorIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
vacationTypes: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
holidays: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const form = useForm({
|
||||||
|
dateFrom: null,
|
||||||
|
dateTo: null,
|
||||||
|
vacationType: props.vacationTypes[0],
|
||||||
|
comment: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const disableDates = [
|
||||||
|
date => (date.getDay() === 0 || date.getDay() === 6),
|
||||||
|
]
|
||||||
|
|
||||||
|
const fromInputConfig = reactive({
|
||||||
|
maxDate: null,
|
||||||
|
disable: disableDates,
|
||||||
|
})
|
||||||
|
|
||||||
|
const toInputConfig = reactive({
|
||||||
|
minDate: null,
|
||||||
|
disable: disableDates,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
form,
|
||||||
|
fromInputConfig,
|
||||||
|
toInputConfig,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
createForm() {
|
||||||
|
this.form
|
||||||
|
.transform(data => ({
|
||||||
|
from: data.dateFrom,
|
||||||
|
to: data.dateTo,
|
||||||
|
type: data.vacationType.value,
|
||||||
|
comment: data.comment,
|
||||||
|
}))
|
||||||
|
.post('/vacation-requests')
|
||||||
|
},
|
||||||
|
onFromChange(selectedDates, dateStr) {
|
||||||
|
this.toInputConfig.minDate = dateStr
|
||||||
|
},
|
||||||
|
onToChange(selectedDates, dateStr) {
|
||||||
|
this.fromInputConfig.maxDate = dateStr
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
217
resources/js/Pages/VacationRequest/Index.vue
Normal file
217
resources/js/Pages/VacationRequest/Index.vue
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<template>
|
||||||
|
<InertiaHead title="Twoje wnioski urlopowe" />
|
||||||
|
<div class="bg-white sm:rounded-lg shadow-md">
|
||||||
|
<div class="flex justify-between items-center p-4 sm:px-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Twoje wnioski urlopowe
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<InertiaLink
|
||||||
|
href="vacation-requests/create"
|
||||||
|
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"
|
||||||
|
>
|
||||||
|
Dodaj wniosek
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto xl:overflow-x-visible overflow-y-auto xl:overflow-y-visible">
|
||||||
|
<nav class="relative shadow flex divide-x divide-gray-200 border-t border-gray-200">
|
||||||
|
<InertiaLink
|
||||||
|
v-for="(status, index) in statuses"
|
||||||
|
:key="index"
|
||||||
|
:data="{ status: status.value }"
|
||||||
|
:class="[status.value === filters.status ? 'text-gray-900' : '', 'text-gray-500 hover:text-gray-700 group relative min-w-0 flex-1 overflow-hidden bg-white py-4 px-4 text-sm font-medium text-center hover:bg-gray-50 focus:z-10']"
|
||||||
|
>
|
||||||
|
<span>{{ status.name }}</span>
|
||||||
|
<span :class="[status.value === filters.status ? 'bg-blumilk-500' : 'bg-transparent', 'absolute inset-x-0 bottom-0 h-0.5']" />
|
||||||
|
</InertiaLink>
|
||||||
|
</nav>
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Numer
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Rodzaj urlopu
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Status
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Od
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Do
|
||||||
|
</th>
|
||||||
|
<th
|
||||||
|
scope="col"
|
||||||
|
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
|
||||||
|
>
|
||||||
|
Dni urlopu
|
||||||
|
</th>
|
||||||
|
<th scope="col" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
|
<tr
|
||||||
|
v-for="request in requests.data"
|
||||||
|
:key="request.id"
|
||||||
|
class="hover:bg-blumilk-25"
|
||||||
|
>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<InertiaLink
|
||||||
|
:href="`/vacation-requests/${request.id}`"
|
||||||
|
class="font-semibold text-blumilk-600 hover:text-blumilk-500 hover:underline"
|
||||||
|
>
|
||||||
|
{{ request.name }}
|
||||||
|
</InertiaLink>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{{ request.type }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{{ request.state }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{{ request.from }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
{{ request.to }}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
X
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||||
|
<InertiaLink :href="`/vacation-requests/${request.id}`">
|
||||||
|
<ChevronRightIcon class="block w-6 h-6 fill-gray-400" />
|
||||||
|
</InertiaLink>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr
|
||||||
|
v-if="! requests.data.length"
|
||||||
|
>
|
||||||
|
<td
|
||||||
|
colspan="100%"
|
||||||
|
class="text-center py-4 text-xl leading-5 text-gray-700"
|
||||||
|
>
|
||||||
|
Brak danych
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div
|
||||||
|
v-if="requests.data.length && requests.meta.last_page !== 1"
|
||||||
|
class="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6 rounded-b-lg"
|
||||||
|
>
|
||||||
|
<div class="flex-1 flex justify-between sm:hidden">
|
||||||
|
<InertiaLink
|
||||||
|
:is="requests.links.prev ? 'InertiaLink': 'span'"
|
||||||
|
:href="requests.links.prev"
|
||||||
|
class="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Poprzednia
|
||||||
|
</InertiaLink>
|
||||||
|
<Component
|
||||||
|
:is="requests.links.next ? 'InertiaLink': 'span'"
|
||||||
|
:href="requests.links.next"
|
||||||
|
class="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Następna
|
||||||
|
</Component>
|
||||||
|
</div>
|
||||||
|
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
|
<div class="text-sm text-gray-700">
|
||||||
|
Wyświetlanie
|
||||||
|
<span class="font-medium">{{ requests.meta.from }}</span>
|
||||||
|
od
|
||||||
|
<span class="font-medium">{{ requests.meta.to }}</span>
|
||||||
|
do
|
||||||
|
<span class="font-medium">{{ requests.meta.total }}</span>
|
||||||
|
wyników
|
||||||
|
</div>
|
||||||
|
<nav class="relative z-0 inline-flex space-x-1">
|
||||||
|
<template
|
||||||
|
v-for="(link, index) in requests.meta.links"
|
||||||
|
:key="index"
|
||||||
|
>
|
||||||
|
<Component
|
||||||
|
:is="link.url ? 'InertiaLink' : 'span'"
|
||||||
|
:href="link.url"
|
||||||
|
:preserve-scroll="true"
|
||||||
|
class="relative inline-flex items-center px-4 py-2 border rounded-md text-sm font-medium"
|
||||||
|
:class="{ 'z-10 bg-blumilk-25 border-blumilk-500 text-blumilk-600': link.active, 'bg-white border-gray-300 text-gray-500': !link.active, 'hover:bg-blumilk-25': link.url, 'border-none': !link.url}"
|
||||||
|
v-text="link.label"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {ChevronRightIcon, DotsVerticalIcon, PencilIcon, TrashIcon} from '@heroicons/vue/solid'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationRequestIndex',
|
||||||
|
components: {
|
||||||
|
DotsVerticalIcon,
|
||||||
|
PencilIcon,
|
||||||
|
TrashIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
requests: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const statuses = [
|
||||||
|
{
|
||||||
|
name: 'Wszystkie',
|
||||||
|
value: 'all',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'W trakcie',
|
||||||
|
value: 'pending',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Zatwierdzone',
|
||||||
|
value: 'success',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Odrzucone/anulowane',
|
||||||
|
value: 'failed',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
statuses,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
216
resources/js/Pages/VacationRequest/Show.vue
Normal file
216
resources/js/Pages/VacationRequest/Show.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<InertiaHead :title="`Wniosek ${request.name}`" />
|
||||||
|
<div class="grid grid-cols-1 gap-6 xl:grid-flow-col-dense xl:grid-cols-3">
|
||||||
|
<div class="space-y-6 xl:col-start-1 xl:col-span-2">
|
||||||
|
<div class="bg-white sm:rounded-lg shadow-md">
|
||||||
|
<div class="px-4 py-5 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Informacje na temat wniosku
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-200 px-4 py-5 sm:p-0">
|
||||||
|
<dl class="sm:divide-y sm:divide-gray-200">
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Nr wniosku
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.name }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Rodzaj urlopu
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.type }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Urlop od
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.from }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Urlop do
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.to }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Dni
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
x
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
<div class="py-4 sm:py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||||
|
<dt class="text-sm font-medium text-gray-500">
|
||||||
|
Komentarz
|
||||||
|
</dt>
|
||||||
|
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
|
||||||
|
{{ request.comment }}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Zaakceptuj wniosek jako osoba techniczna
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||||
|
<p>
|
||||||
|
W zależności od typu wniosku, zostanie on zatwierdzony lub osoba administracyjna będzie musiała go zaakceptować.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<InertiaLink
|
||||||
|
:href="`/vacation-requests/${request.id}/accept-as-technical`"
|
||||||
|
method="post"
|
||||||
|
as="button"
|
||||||
|
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
|
>
|
||||||
|
Zaakceptuj wniosek
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Zaakceptuj wniosek jako osoba administracyjna
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||||
|
<p>
|
||||||
|
Po akceptacji przez osobę administracyjną, wniosek zostanie zatwierdzony.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<InertiaLink
|
||||||
|
:href="`/vacation-requests/${request.id}/accept-as-administrative`"
|
||||||
|
method="post"
|
||||||
|
as="button"
|
||||||
|
class="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-blumilk-600 hover:bg-blumilk-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blumilk-500"
|
||||||
|
>
|
||||||
|
Zaakceptuj wniosek
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Odrzuć wniosek
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||||
|
<p>
|
||||||
|
Odrzuconego wniosku nie można przywracać - należy zrobić nowy.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<InertiaLink
|
||||||
|
:href="`/vacation-requests/${request.id}/reject`"
|
||||||
|
method="post"
|
||||||
|
as="button"
|
||||||
|
class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||||
|
>
|
||||||
|
Odrzuć wniosek
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-white shadow sm:rounded-lg border border-red-500">
|
||||||
|
<div class="px-4 py-5 sm:p-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Anuluj wniosek
|
||||||
|
</h3>
|
||||||
|
<div class="mt-2 max-w-xl text-sm text-gray-500">
|
||||||
|
<p>
|
||||||
|
Wniosek można anulować w każdej chwili - nawet jeśli był już zatwierdzony.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<InertiaLink
|
||||||
|
:href="`/vacation-requests/${request.id}/cancel`"
|
||||||
|
method="post"
|
||||||
|
as="button"
|
||||||
|
class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||||
|
>
|
||||||
|
Anuluj wniosek
|
||||||
|
</InertiaLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="xl:col-start-3 xl:col-span-1 space-y-6">
|
||||||
|
<div class="bg-white sm:rounded-lg shadow-md">
|
||||||
|
<div class="px-4 py-5 sm:px-6">
|
||||||
|
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||||
|
Historia wniosku
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-200 px-4 py-4">
|
||||||
|
<ul>
|
||||||
|
<li
|
||||||
|
v-for="(activity, index) in activities.data"
|
||||||
|
:key="activity.id"
|
||||||
|
>
|
||||||
|
<div :class="{'relative pb-8': index !== activities.data.length - 1}">
|
||||||
|
<span
|
||||||
|
v-if="(index !== activities.data.length - 1)"
|
||||||
|
class="absolute top-4 left-4 -ml-px h-full w-0.5 bg-gray-200"
|
||||||
|
/>
|
||||||
|
<div class="relative flex space-x-3">
|
||||||
|
<div>
|
||||||
|
<span class="bg-blumilk-500 h-8 w-8 rounded-full flex items-center justify-center ring-8 ring-white">
|
||||||
|
<ThumbUpIcon class="w-5 h-5 text-white" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0 flex-1 pt-1.5 flex justify-between space-x-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
{{ activity.to }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="text-right text-sm whitespace-nowrap text-gray-500">
|
||||||
|
<time>{{ activity.date }}</time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { ThumbUpIcon } from '@heroicons/vue/outline'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VacationRequestShow',
|
||||||
|
components: {
|
||||||
|
ThumbUpIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
request: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
activities: {
|
||||||
|
type: Object,
|
||||||
|
default: () => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
@ -8,5 +8,5 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'GuestLayout',
|
name: 'GuestLayout',
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,38 +1,38 @@
|
|||||||
import {createApp, h} from 'vue';
|
import {createApp, h} from 'vue'
|
||||||
import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3';
|
import {createInertiaApp, Head, Link} from '@inertiajs/inertia-vue3'
|
||||||
import {InertiaProgress} from '@inertiajs/progress';
|
import {InertiaProgress} from '@inertiajs/progress'
|
||||||
import AppLayout from '@/Shared/Layout/AppLayout';
|
import AppLayout from '@/Shared/Layout/AppLayout'
|
||||||
import Flatpickr from 'flatpickr';
|
import Flatpickr from 'flatpickr'
|
||||||
import { Polish } from 'flatpickr/dist/l10n/pl.js';
|
import { Polish } from 'flatpickr/dist/l10n/pl.js'
|
||||||
|
|
||||||
createInertiaApp({
|
createInertiaApp({
|
||||||
resolve: name => {
|
resolve: name => {
|
||||||
const page = require(`./Pages/${name}`).default;
|
const page = require(`./Pages/${name}`).default
|
||||||
|
|
||||||
page.layout = page.layout || AppLayout;
|
page.layout = page.layout || AppLayout
|
||||||
|
|
||||||
return page;
|
return page
|
||||||
},
|
},
|
||||||
setup({el, App, props, plugin}) {
|
setup({el, App, props, plugin}) {
|
||||||
createApp({render: () => h(App, props)})
|
createApp({render: () => h(App, props)})
|
||||||
.use(plugin)
|
.use(plugin)
|
||||||
.component('InertiaLink', Link)
|
.component('InertiaLink', Link)
|
||||||
.component('InertiaHead', Head)
|
.component('InertiaHead', Head)
|
||||||
.mount(el);
|
.mount(el)
|
||||||
},
|
},
|
||||||
title: title => `${title} - Toby`,
|
title: title => `${title} - Toby`,
|
||||||
});
|
})
|
||||||
|
|
||||||
InertiaProgress.init({
|
InertiaProgress.init({
|
||||||
delay: 0,
|
delay: 0,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
});
|
})
|
||||||
|
|
||||||
Flatpickr.localize(Polish);
|
Flatpickr.localize(Polish)
|
||||||
Flatpickr.setDefaults({
|
Flatpickr.setDefaults({
|
||||||
dateFormat: 'Y-m-d',
|
dateFormat: 'Y-m-d',
|
||||||
enableTime: false,
|
enableTime: false,
|
||||||
altFormat: 'j F Y',
|
altFormat: 'j F Y',
|
||||||
altInput: true,
|
altInput: true,
|
||||||
});
|
})
|
||||||
|
|
||||||
|
@ -3,5 +3,27 @@
|
|||||||
"employment_contract": "Umowa o pracę",
|
"employment_contract": "Umowa o pracę",
|
||||||
"commission_contract": "Umowa zlecenie",
|
"commission_contract": "Umowa zlecenie",
|
||||||
"b2b_contract": "Kontrakt B2B",
|
"b2b_contract": "Kontrakt B2B",
|
||||||
"board_member_contract": "Członek zarządu"
|
"board_member_contract": "Członek zarządu",
|
||||||
|
"vacation": "Urlop wypoczynkowy",
|
||||||
|
"vacation_on_request": "Urlop na żądanie",
|
||||||
|
"special_vacation": "Urlop okolicznościowy",
|
||||||
|
"childcare_vacation": "Opieka nad dzieckiem art 188 kp",
|
||||||
|
"training_vacation": "Urlop szkoleniowy",
|
||||||
|
"unpaid_vacation": "Urlop bezpłatny",
|
||||||
|
"volunteering_vacation": "Wolontariat",
|
||||||
|
"look_for_work_vacation": "Urlop na poszukiwanie pracy",
|
||||||
|
"time_in_lieu": "Odbiór za święto",
|
||||||
|
"sick_vacation": "Zwolnienie lekarskie",
|
||||||
|
"employee": "Pracownik",
|
||||||
|
"administrator": "Administrator",
|
||||||
|
"technical_approver": "Techniczny klepacz",
|
||||||
|
"administrative_approver": "Administracyjny klepacz",
|
||||||
|
"created": "Utworzony",
|
||||||
|
"canceled": "Anulowany",
|
||||||
|
"rejected": "Odrzucony",
|
||||||
|
"approved": "Zatwierdzony",
|
||||||
|
"waiting_for_technical": "Czeka na akceptację od technicznego",
|
||||||
|
"waiting_for_administrative": "Czeka na akceptację od administracyjnego",
|
||||||
|
"accepted_by_technical": "Zaakceptowany przez technicznego",
|
||||||
|
"accepted_by_administrative": "Zaakceptowany przez administracyjnego"
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,17 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Illuminate\Support\Facades\View;
|
|
||||||
use Toby\Infrastructure\Http\Controllers\GoogleController;
|
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\UserController;
|
use Toby\Infrastructure\Http\Controllers\UserController;
|
||||||
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
use Toby\Infrastructure\Http\Controllers\VacationLimitController;
|
||||||
|
use Toby\Infrastructure\Http\Controllers\VacationRequestController;
|
||||||
|
|
||||||
Route::middleware("auth")->group(function (): void {
|
Route::middleware("auth")->group(function (): void {
|
||||||
Route::get("/", fn() => inertia("Dashboard"))->name("dashboard");
|
Route::get("/", fn() => inertia("Dashboard"))
|
||||||
|
->name("dashboard");
|
||||||
Route::post("/logout", LogoutController::class);
|
Route::post("/logout", LogoutController::class);
|
||||||
|
|
||||||
Route::resource("users", UserController::class);
|
Route::resource("users", UserController::class);
|
||||||
@ -20,16 +21,37 @@ Route::middleware("auth")->group(function (): void {
|
|||||||
|
|
||||||
Route::resource("holidays", HolidayController::class);
|
Route::resource("holidays", HolidayController::class);
|
||||||
|
|
||||||
|
Route::get("/vacation-limits", [VacationLimitController::class, "edit"])
|
||||||
|
->name("vacation.limits");
|
||||||
Route::get("/calendar", [HolidayController::class,"showCalendar"]);
|
Route::get("/calendar", [HolidayController::class,"showCalendar"]);
|
||||||
|
|
||||||
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"]);
|
||||||
|
|
||||||
Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)->name("year-periods.select");
|
Route::get("/vacation-requests", [VacationRequestController::class, "index"])
|
||||||
|
->name("vacation.requests.index");
|
||||||
|
Route::get("/vacation-requests/create", [VacationRequestController::class, "create"])
|
||||||
|
->name("vacation.requests.create");
|
||||||
|
Route::post("/vacation-requests", [VacationRequestController::class, "store"])
|
||||||
|
->name("vacation.requests.store");
|
||||||
|
Route::get("/vacation-requests/{vacationRequest}", [VacationRequestController::class, "show"])
|
||||||
|
->name("vacation.requests.show");
|
||||||
|
Route::post("/vacation-requests/{vacationRequest}/reject", [VacationRequestController::class, "reject"])
|
||||||
|
->name("vacation.requests.reject");
|
||||||
|
Route::post("/vacation-requests/{vacationRequest}/cancel", [VacationRequestController::class, "cancel"])
|
||||||
|
->name("vacation.requests.cancel");
|
||||||
|
Route::post("/vacation-requests/{vacationRequest}/accept-as-technical", [VacationRequestController::class, "acceptAsTechnical"])
|
||||||
|
->name("vacation.requests.accept-as-technical");
|
||||||
|
Route::post("/vacation-requests/{vacationRequest}/accept-as-administrative", [VacationRequestController::class, "acceptAsAdministrative"])
|
||||||
|
->name("vacation.requests.accept-as-administrative");
|
||||||
|
|
||||||
|
Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)
|
||||||
|
->name("year-periods.select");
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware("guest")->group(function (): void {
|
Route::middleware("guest")->group(function (): void {
|
||||||
Route::get("login", fn() => inertia("Login"))->name("login");
|
Route::get("login", fn() => inertia("Login"))
|
||||||
|
->name("login");
|
||||||
Route::get("login/google/start", [GoogleController::class, "redirect"])
|
Route::get("login/google/start", [GoogleController::class, "redirect"])
|
||||||
->name("login.google.start");
|
->name("login.google.start");
|
||||||
Route::get("login/google/end", [GoogleController::class, "callback"])
|
Route::get("login/google/end", [GoogleController::class, "callback"])
|
||||||
|
@ -8,7 +8,8 @@ use Illuminate\Foundation\Testing\DatabaseMigrations;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Inertia\Testing\AssertableInertia as Assert;
|
use Inertia\Testing\AssertableInertia as Assert;
|
||||||
use Tests\FeatureTestCase;
|
use Tests\FeatureTestCase;
|
||||||
use Toby\Domain\EmploymentForm;
|
use Toby\Domain\Enums\EmploymentForm;
|
||||||
|
use Toby\Domain\Enums\Role;
|
||||||
use Toby\Eloquent\Models\User;
|
use Toby\Eloquent\Models\User;
|
||||||
|
|
||||||
class UserTest extends FeatureTestCase
|
class UserTest extends FeatureTestCase
|
||||||
@ -87,6 +88,7 @@ class UserTest extends FeatureTestCase
|
|||||||
->post("/users", [
|
->post("/users", [
|
||||||
"firstName" => "John",
|
"firstName" => "John",
|
||||||
"lastName" => "Doe",
|
"lastName" => "Doe",
|
||||||
|
"role" => Role::EMPLOYEE->value,
|
||||||
"email" => "john.doe@example.com",
|
"email" => "john.doe@example.com",
|
||||||
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
||||||
"employmentDate" => Carbon::now()->toDateString(),
|
"employmentDate" => Carbon::now()->toDateString(),
|
||||||
@ -122,6 +124,7 @@ class UserTest extends FeatureTestCase
|
|||||||
"firstName" => "John",
|
"firstName" => "John",
|
||||||
"lastName" => "Doe",
|
"lastName" => "Doe",
|
||||||
"email" => "john.doe@example.com",
|
"email" => "john.doe@example.com",
|
||||||
|
"role" => Role::EMPLOYEE->value,
|
||||||
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
"employmentForm" => EmploymentForm::B2B_CONTRACT->value,
|
||||||
"employmentDate" => Carbon::now()->toDateString(),
|
"employmentDate" => Carbon::now()->toDateString(),
|
||||||
])
|
])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user