diff --git a/app/Domain/CalendarGenerator.php b/app/Domain/CalendarGenerator.php new file mode 100644 index 0000000..f1d72f2 --- /dev/null +++ b/app/Domain/CalendarGenerator.php @@ -0,0 +1,80 @@ +year, $this->monthNameToNumber($month)); + $period = CarbonPeriod::create($date->startOfMonth(), $date->endOfMonth()); + $holidays = $yearPeriod->holidays()->pluck("date"); + + return $this->generateCalendar($period, $holidays); + } + + protected function monthNameToNumber($name): int + { + return match ($name) { + default => CarbonInterface::JANUARY, + "february" => CarbonInterface::FEBRUARY, + "march" => CarbonInterface::MARCH, + "april" => CarbonInterface::APRIL, + "may" => CarbonInterface::MAY, + "june" => CarbonInterface::JUNE, + "july" => CarbonInterface::JULY, + "august" => CarbonInterface::AUGUST, + "september" => CarbonInterface::SEPTEMBER, + "october" => CarbonInterface::OCTOBER, + "november" => CarbonInterface::NOVEMBER, + "december" => CarbonInterface::DECEMBER, + }; + } + + protected function generateCalendar(CarbonPeriod $period, Collection $holidays): array + { + $calendar = []; + $vacations = $this->getVacationsForPeriod($period); + + foreach ($period as $day) { + $vacationsForDay = $vacations[$day->toDateString()] ?? new Collection(); + + $calendar[] = [ + "date" => $day->toDateString(), + "dayOfMonth" => $day->translatedFormat("j"), + "dayOfWeek" => $day->translatedFormat("D"), + "isToday" => $day->isToday(), + "isWeekend" => $day->isWeekend(), + "isHoliday" => $holidays->contains($day), + "vacations" => $vacationsForDay->pluck("user_id"), + ]; + } + + return $calendar; + } + + protected function getVacationsForPeriod(CarbonPeriod $period): Collection + { + return Vacation::query() + ->whereBetween("date", [$period->start, $period->end]) + ->whereRelation("vacationRequest", "state", VacationRequestState::Approved->value) + ->get() + ->groupBy(fn(Vacation $vacation) => $vacation->date->toDateString()); + } +} diff --git a/app/Domain/Enums/EmploymentForm.php b/app/Domain/Enums/EmploymentForm.php index cdaa241..625b801 100644 --- a/app/Domain/Enums/EmploymentForm.php +++ b/app/Domain/Enums/EmploymentForm.php @@ -6,10 +6,10 @@ namespace Toby\Domain\Enums; enum EmploymentForm: string { - case EMPLOYMENT_CONTRACT = "employment_contract"; - case COMMISSION_CONTRACT = "commission_contract"; - case B2B_CONTRACT = "b2b_contract"; - case BOARD_MEMBER_CONTRACT = "board_member_contract"; + case EmploymentContract = "employment_contract"; + case ComissionContract = "commission_contract"; + case B2bContract = "b2b_contract"; + case BoardMemberContract = "board_member_contract"; public function label(): string { diff --git a/app/Domain/Enums/Role.php b/app/Domain/Enums/Role.php index 7f6a996..f4be8b9 100644 --- a/app/Domain/Enums/Role.php +++ b/app/Domain/Enums/Role.php @@ -6,10 +6,10 @@ namespace Toby\Domain\Enums; enum Role: string { - case EMPLOYEE = "employee"; - case ADMINISTRATOR = "administrator"; - case TECHNICAL_APPROVER = "technical_approver"; - case ADMINISTRATIVE_APPROVER = "administrative_approver"; + case Employee = "employee"; + case Administrator = "administrator"; + case TechnicalApprover = "technical_approver"; + case AdministrativeApprover = "administrative_approver"; public function label(): string { diff --git a/app/Domain/Enums/VacationRequestState.php b/app/Domain/Enums/VacationRequestState.php index cd7ec57..7fcbae5 100644 --- a/app/Domain/Enums/VacationRequestState.php +++ b/app/Domain/Enums/VacationRequestState.php @@ -6,14 +6,14 @@ 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"; + case Created = "created"; + case Canceled = "canceled"; + case Rejected = "rejected"; + case Approved = "approved"; + case WaitingForTechnical = "waiting_for_technical"; + case WaitingForAdministrative = "waiting_for_administrative"; + case AcceptedByTechnical = "accepted_by_technical"; + case AcceptedByAdministrative = "accepted_by_administrative"; public function label(): string { @@ -23,24 +23,24 @@ enum VacationRequestState: string public static function pendingStates(): array { return [ - self::CREATED, - self::WAITING_FOR_TECHNICAL, - self::WAITING_FOR_ADMINISTRATIVE, - self::ACCEPTED_BY_TECHNICAL, - self::ACCEPTED_BY_ADMINSTRATIVE, + self::Created, + self::WaitingForTechnical, + self::WaitingForAdministrative, + self::AcceptedByTechnical, + self::AcceptedByAdministrative, ]; } public static function successStates(): array { - return [self::APPROVED]; + return [self::Approved]; } public static function failedStates(): array { return [ - self::REJECTED, - self::CANCELED, + self::Rejected, + self::Canceled, ]; } diff --git a/app/Domain/Enums/VacationType.php b/app/Domain/Enums/VacationType.php index c3502c8..63e2858 100644 --- a/app/Domain/Enums/VacationType.php +++ b/app/Domain/Enums/VacationType.php @@ -6,15 +6,15 @@ 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"; + case Vacation = "vacation"; + case OnRequest = "vacation_on_request"; + case Special = "special_vacation"; + case Childcare = "childcare_vacation"; + case Training = "training_vacation"; + case Unpaid = "unpaid_vacation"; + case Volunteering = "volunteering_vacation"; + case TimeInLieu = "time_in_lieu"; + case Sick = "sick_vacation"; public function label(): string { diff --git a/app/Domain/VacationDaysCalculator.php b/app/Domain/VacationDaysCalculator.php index fcbc9e8..0e41a4a 100644 --- a/app/Domain/VacationDaysCalculator.php +++ b/app/Domain/VacationDaysCalculator.php @@ -16,7 +16,7 @@ class VacationDaysCalculator $period = CarbonPeriod::create($from, $to); $holidays = $yearPeriod->holidays()->pluck("date"); - $validDays = collect(); + $validDays = new Collection(); foreach ($period as $day) { if ($this->passes($day, $holidays)) { diff --git a/app/Domain/VacationRequestStateManager.php b/app/Domain/VacationRequestStateManager.php index 8bee7c7..7de8484 100644 --- a/app/Domain/VacationRequestStateManager.php +++ b/app/Domain/VacationRequestStateManager.php @@ -23,50 +23,50 @@ class VacationRequestStateManager public function markAsCreated(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::CREATED); + $this->changeState($vacationRequest, VacationRequestState::Created); $this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest)); } public function approve(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::APPROVED); + $this->changeState($vacationRequest, VacationRequestState::Approved); $this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest)); } public function reject(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::REJECTED); + $this->changeState($vacationRequest, VacationRequestState::Rejected); } public function cancel(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::CANCELED); + $this->changeState($vacationRequest, VacationRequestState::Canceled); } public function acceptAsTechnical(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_TECHNICAL); + $this->changeState($vacationRequest, VacationRequestState::AcceptedByTechnical); $this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest)); } public function acceptAsAdministrative(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::ACCEPTED_BY_ADMINSTRATIVE); + $this->changeState($vacationRequest, VacationRequestState::AcceptedByAdministrative); $this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest)); } public function waitForTechnical(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_TECHNICAL); + $this->changeState($vacationRequest, VacationRequestState::WaitingForTechnical); } public function waitForAdministrative(VacationRequest $vacationRequest): void { - $this->changeState($vacationRequest, VacationRequestState::WAITING_FOR_ADMINISTRATIVE); + $this->changeState($vacationRequest, VacationRequestState::WaitingForAdministrative); } protected function changeState(VacationRequest $vacationRequest, VacationRequestState $state): void diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index c9c4148..c8c9ab5 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -4,23 +4,64 @@ declare(strict_types=1); namespace Toby\Domain\Validation\Rules; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Collection; +use Toby\Domain\Enums\VacationRequestState; +use Toby\Domain\Enums\VacationType; +use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationTypeConfigRetriever; +use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; +use Toby\Eloquent\Models\YearPeriod; class DoesNotExceedLimitRule implements VacationRequestRule { public function __construct( protected VacationTypeConfigRetriever $configRetriever, + protected VacationDaysCalculator $vacationDaysCalculator, ) { } public function check(VacationRequest $vacationRequest): bool { - return true; + if (!$this->configRetriever->hasLimit($vacationRequest->type)) { + return true; + } + + $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); + $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); + $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count(); + + return $limit >= ($vacationDays + $estimatedDays); } public function errorMessage(): string { - return __("You have exceeded your vacation limit."); + return __("Vacation limit has been exceeded."); + } + + protected function getUserVacationLimit(User $user, YearPeriod $yearPeriod): int + { + return $user->vacationLimits()->where("year_period_id", $yearPeriod->id)->first()->days ?? 0; + } + + protected function getVacationDaysWithLimit(User $user, YearPeriod $yearPeriod): int + { + return $user->vacations() + ->where("year_period_id", $yearPeriod->id) + ->whereRelation( + "vacationRequest", + fn(Builder $query) => $query + ->whereIn("type", $this->getLimitableVacationTypes()) + ->noStates(VacationRequestState::failedStates()), + ) + ->count(); + } + + protected function getLimitableVacationTypes(): Collection + { + $types = new Collection(VacationType::cases()); + + return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); } } diff --git a/app/Eloquent/Models/User.php b/app/Eloquent/Models/User.php index 6a7c4db..1f8c6e3 100644 --- a/app/Eloquent/Models/User.php +++ b/app/Eloquent/Models/User.php @@ -28,6 +28,7 @@ use Toby\Domain\Enums\Role; * @property Carbon $employment_date * @property Collection $vacationLimits * @property Collection $vacationRequests + * @property Collection $vacations */ class User extends Authenticatable { @@ -57,6 +58,11 @@ class User extends Authenticatable return $this->hasMany(VacationRequest::class); } + public function vacations(): HasMany + { + return $this->hasMany(Vacation::class); + } + public function scopeSearch(Builder $query, ?string $text): Builder { if ($text === null) { diff --git a/app/Eloquent/Models/Vacation.php b/app/Eloquent/Models/Vacation.php new file mode 100644 index 0000000..c22cf76 --- /dev/null +++ b/app/Eloquent/Models/Vacation.php @@ -0,0 +1,43 @@ + "date", + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(User::class); + } + + public function vacationRequest(): BelongsTo + { + return $this->belongsTo(VacationRequest::class); + } + + public function yearPeriod(): BelongsTo + { + return $this->belongsTo(YearPeriod::class); + } +} diff --git a/app/Eloquent/Models/VacationRequest.php b/app/Eloquent/Models/VacationRequest.php index 0a81620..f8d29b9 100644 --- a/app/Eloquent/Models/VacationRequest.php +++ b/app/Eloquent/Models/VacationRequest.php @@ -21,11 +21,11 @@ use Toby\Domain\Enums\VacationType; * @property VacationRequestState $state * @property Carbon $from * @property Carbon $to - * @property int $estimated_days * @property string $comment * @property User $user * @property YearPeriod $yearPeriod * @property Collection $activities + * @property Collection $vacations * @property Carbon $created_at * @property Carbon $updated_at */ @@ -57,6 +57,11 @@ class VacationRequest extends Model return $this->hasMany(VacationRequestActivity::class); } + public function vacations(): HasMany + { + return $this->hasMany(Vacation::class); + } + public function changeStateTo(VacationRequestState $state): void { $this->state = $state; @@ -69,6 +74,11 @@ class VacationRequest extends Model return $query->whereIn("state", $states); } + public function scopeNoStates(Builder $query, array $states): Builder + { + return $query->whereNotIn("state", $states); + } + public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder { return $query->where("from", "<=", $vacationRequest->to) diff --git a/app/Infrastructure/Http/Controllers/VacationCalendarController.php b/app/Infrastructure/Http/Controllers/VacationCalendarController.php new file mode 100644 index 0000000..d37cce6 --- /dev/null +++ b/app/Infrastructure/Http/Controllers/VacationCalendarController.php @@ -0,0 +1,38 @@ +query("month", Carbon::now()->englishMonth)); + $yearPeriod = $yearPeriodRetriever->selected(); + $users = User::query() + ->orderBy("last_name") + ->orderBy("first_name") + ->get(); + + $calendar = $calendarGenerator->generate($yearPeriod, $month); + + return inertia("Calendar", [ + "calendar" => $calendar, + "currentMonth" => $month, + "users" => UserResource::collection($users), + ]); + } +} diff --git a/app/Infrastructure/Http/Controllers/VacationRequestController.php b/app/Infrastructure/Http/Controllers/VacationRequestController.php index ca8be78..814b35d 100644 --- a/app/Infrastructure/Http/Controllers/VacationRequestController.php +++ b/app/Infrastructure/Http/Controllers/VacationRequestController.php @@ -24,16 +24,21 @@ class VacationRequestController extends Controller { public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response { + $status = $request->get("status", "all"); + $vacationRequests = $request->user() ->vacationRequests() + ->with("vacations") ->where("year_period_id", $yearPeriodRetriever->selected()->id) ->latest() - ->states(VacationRequestState::filterByStatus($request->query("status", "all"))) + ->states(VacationRequestState::filterByStatus($status)) ->paginate(); return inertia("VacationRequest/Index", [ "requests" => VacationRequestResource::collection($vacationRequests), - "filters" => $request->only("status"), + "filters" => [ + "status" => $status, + ], ]); } @@ -69,15 +74,24 @@ class VacationRequestController extends Controller ): RedirectResponse { /** @var VacationRequest $vacationRequest */ $vacationRequest = $request->user()->vacationRequests()->make($request->data()); - $vacationRequest->estimated_days = $vacationDaysCalculator->calculateDays( - $vacationRequest->yearPeriod, - $vacationRequest->from, - $vacationRequest->to, - )->count(); - $vacationRequestValidator->validate($vacationRequest); $vacationRequest->save(); + + $days = $vacationDaysCalculator->calculateDays( + $vacationRequest->yearPeriod, + $vacationRequest->from, + $vacationRequest->to, + ); + + foreach ($days as $day) { + $vacationRequest->vacations()->create([ + "date" => $day, + "user_id" => $vacationRequest->user->id, + "year_period_id" => $vacationRequest->yearPeriod->id, + ]); + } + $stateManager->markAsCreated($vacationRequest); return redirect() diff --git a/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php b/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php index dcc11d2..c513785 100644 --- a/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php +++ b/app/Infrastructure/Http/Resources/VacationRequestActivityResource.php @@ -13,9 +13,11 @@ class VacationRequestActivityResource extends JsonResource public function toArray($request): array { return [ - "date" => $this->created_at->toDisplayString(), - "who" => $this->user ? $this->user->fullName : __("System"), - "to" => $this->to->label(), + "date" => $this->created_at->format("d.m.Y"), + "time" => $this->created_at->format("H:i"), + "user" => $this->user ? $this->user->fullName : __("System"), + "state" => $this->to, + "text" => $this->to->label(), ]; } } diff --git a/app/Infrastructure/Http/Resources/VacationRequestResource.php b/app/Infrastructure/Http/Resources/VacationRequestResource.php index 009eeb6..112d7a0 100644 --- a/app/Infrastructure/Http/Resources/VacationRequestResource.php +++ b/app/Infrastructure/Http/Resources/VacationRequestResource.php @@ -17,11 +17,11 @@ class VacationRequestResource extends JsonResource "name" => $this->name, "user" => new UserResource($this->user), "type" => $this->type->label(), - "state" => $this->state->label(), + "state" => $this->state, "from" => $this->from->toDisplayString(), "to" => $this->to->toDisplayString(), - "estimatedDays" => $this->estimated_days, "comment" => $this->comment, + "days" => VacationResource::collection($this->vacations), ]; } } diff --git a/app/Infrastructure/Http/Resources/VacationResource.php b/app/Infrastructure/Http/Resources/VacationResource.php new file mode 100644 index 0000000..e9590e3 --- /dev/null +++ b/app/Infrastructure/Http/Resources/VacationResource.php @@ -0,0 +1,21 @@ + $this->id, + "displayDate" => $this->date->toDisplayString(), + "date" => $this->date->toDateString(), + ]; + } +} diff --git a/config/vacation_types.php b/config/vacation_types.php index 5039dfe..45a5900 100644 --- a/config/vacation_types.php +++ b/config/vacation_types.php @@ -6,55 +6,55 @@ use Toby\Domain\Enums\VacationType; use Toby\Domain\VacationTypeConfigRetriever; return [ - VacationType::VACATION->value => [ + 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 => [ + VacationType::OnRequest->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 => [ + VacationType::TimeInLieu->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::SICK_VACATION->value => [ + VacationType::Sick->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::UNPAID_VACATION->value => [ + VacationType::Unpaid->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::SPECIAL_VACATION->value => [ + VacationType::Special->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::CHILDCARE_VACATION->value => [ + VacationType::Childcare->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::TRAINING_VACATION->value => [ + VacationType::Training->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, ], - VacationType::VOLUNTEERING_VACATION->value => [ + VacationType::Volunteering->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index c6a5d93..23f0ea4 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -23,7 +23,7 @@ class UserFactory extends Factory "email" => $this->faker->unique()->safeEmail(), "employment_form" => $this->faker->randomElement(EmploymentForm::cases()), "position" => $this->faker->jobTitle(), - "role" => Role::EMPLOYEE, + "role" => Role::Employee, "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), "remember_token" => Str::random(10), ]; diff --git a/database/factories/VacationFactory.php b/database/factories/VacationFactory.php new file mode 100644 index 0000000..d70c43d --- /dev/null +++ b/database/factories/VacationFactory.php @@ -0,0 +1,15 @@ + $this->faker->randomElement(VacationRequestState::cases()), "from" => $from, "to" => $from->addDays($days), - "estimated_days" => fn(array $attributes) => $this->estimateDays($attributes), "comment" => $this->faker->boolean ? $this->faker->paragraph() : null, ]; } @@ -43,11 +41,4 @@ class VacationRequestFactory extends Factory return "{$number}/{$year}"; } - - protected function estimateDays(array $attributes): int - { - $period = CarbonPeriod::create($attributes["from"], $attributes["to"]); - - return $period->count(); - } } diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index 5f09111..7b3f574 100644 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -16,7 +16,7 @@ return new class() extends Migration { $table->string("last_name"); $table->string("email")->unique(); $table->string("avatar")->nullable(); - $table->string("role")->default(Role::EMPLOYEE->value); + $table->string("role")->default(Role::Employee->value); $table->string("position"); $table->string("employment_form"); $table->date("employment_date"); diff --git a/database/migrations/2022_01_26_100039_create_vacation_requests_table.php b/database/migrations/2022_01_26_100039_create_vacation_requests_table.php index b3f4e48..f5270da 100644 --- a/database/migrations/2022_01_26_100039_create_vacation_requests_table.php +++ b/database/migrations/2022_01_26_100039_create_vacation_requests_table.php @@ -18,7 +18,6 @@ return new class() extends Migration { $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); $table->string("type"); $table->string("state")->nullable(); - $table->integer("estimated_days"); $table->date("from"); $table->date("to"); $table->text("comment")->nullable(); diff --git a/database/migrations/2022_02_07_133018_create_vacations_table.php b/database/migrations/2022_02_07_133018_create_vacations_table.php new file mode 100644 index 0000000..648be3c --- /dev/null +++ b/database/migrations/2022_02_07_133018_create_vacations_table.php @@ -0,0 +1,28 @@ +id(); + $table->foreignIdFor(User::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(VacationRequest::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete(); + $table->date("date"); + }); + } + + public function down(): void + { + Schema::dropIfExists("vacations"); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 205246a..614d712 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -8,6 +8,7 @@ use Illuminate\Database\Seeder; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Toby\Domain\PolishHolidaysRetriever; +use Toby\Domain\VacationDaysCalculator; use Toby\Eloquent\Helpers\UserAvatarGenerator; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; @@ -79,6 +80,21 @@ class DatabaseSeeder extends Seeder ->sequence(fn() => [ "year_period_id" => $yearPeriods->random()->id, ]) + ->afterCreating(function (VacationRequest $vacationRequest): void { + $days = app(VacationDaysCalculator::class)->calculateDays( + $vacationRequest->yearPeriod, + $vacationRequest->from, + $vacationRequest->to, + ); + + foreach ($days as $day) { + $vacationRequest->vacations()->create([ + "date" => $day, + "user_id" => $vacationRequest->user->id, + "year_period_id" => $vacationRequest->yearPeriod->id, + ]); + } + }) ->create(); } } diff --git a/resources/js/Composables/monthInfo.js b/resources/js/Composables/monthInfo.js new file mode 100644 index 0000000..3d7af0a --- /dev/null +++ b/resources/js/Composables/monthInfo.js @@ -0,0 +1,60 @@ +const months = [ + { + 'name': 'Styczeń', + 'value': 'january', + }, + { + 'name': 'Luty', + 'value': 'february', + }, + { + 'name': 'Marzec', + 'value': 'march', + }, + { + 'name': 'Kwiecień', + 'value': 'april', + }, + { + 'name': 'Maj', + 'value': 'may', + }, + { + 'name': 'Czerwiec', + 'value': 'june', + }, + { + 'name': 'Lipiec', + 'value': 'july', + }, + { + 'name': 'Sierpień', + 'value': 'august', + }, + { + 'name': 'Wrzesień', + 'value': 'september', + }, + { + 'name': 'Październik', + 'value': 'october', + }, + { + 'name': 'Listopad', + 'value': 'november', + }, + { + 'name': 'Grudzień', + 'value': 'december', + }, +] + +export function useMonthInfo() { + const getMonths = () => months + const findMonth = value => months.find(month => month.value === value) + + return { + getMonths, + findMonth, + } +} diff --git a/resources/js/Composables/statusInfo.js b/resources/js/Composables/statusInfo.js new file mode 100644 index 0000000..94026d7 --- /dev/null +++ b/resources/js/Composables/statusInfo.js @@ -0,0 +1,128 @@ +import { + CheckIcon as OutlineCheckIcon, + ClockIcon as OutlineClockIcon, + DocumentTextIcon as OutlineDocumentTextIcon, + ThumbDownIcon as OutlineThumbDownIcon, + ThumbUpIcon as OutlineThumbUpIcon, + XIcon as OutlineXIcon, +} from '@heroicons/vue/outline' + +import { + CheckIcon as SolidCheckIcon, + ClockIcon as SolidClockIcon, + DocumentTextIcon as SolidDocumentTextIcon, + ThumbDownIcon as SolidThumbDownIcon, + ThumbUpIcon as SolidThumbUpIcon, + XIcon as SolidXIcon, +} from '@heroicons/vue/solid' + +const statuses = [ + { + text: 'Utworzony', + value: 'created', + outline: { + icon: OutlineDocumentTextIcon, + foreground: 'text-white', + background: 'bg-gray-400', + }, + solid: { + icon: SolidDocumentTextIcon, + color: 'text-gray-400', + }, + }, + { + text: 'Czeka na akceptację od przełożonego technicznego', + value: 'waiting_for_technical', + outline: { + icon: OutlineClockIcon, + foreground: 'text-white', + background: 'bg-amber-400', + }, + solid: { + icon: SolidClockIcon, + color: 'text-amber-400', + }, + }, + { + text: 'Czeka na akceptację od przełożonego administracyjnego', + value: 'waiting_for_administrative', + outline: { + icon: OutlineClockIcon, + foreground: 'text-white', + background: 'bg-amber-400', + }, + solid: { + icon: SolidClockIcon, + color: 'text-amber-400', + }, + }, + { + text: 'Odrzucony', + value: 'rejected', + outline: { + icon: OutlineThumbDownIcon, + foreground: 'text-white', + background: 'bg-rose-600', + }, + solid: { + icon: SolidThumbDownIcon, + color: 'text-rose-600', + }, + }, + { + text: 'Zaakceptowany przez przełożonego technicznego', + value: 'accepted_by_technical', + outline: { + icon: OutlineThumbUpIcon, + foreground: 'text-white', + background: 'bg-green-500', + }, + solid: { + icon: SolidThumbUpIcon, + color: 'text-green-500', + }, + }, + { + text: 'Zaakceptowany przez przełożonego administracyjnego', + value: 'accepted_by_administrative', + outline: { + icon: OutlineThumbUpIcon, + foreground: 'text-white', + background: 'bg-green-500', + }, + solid: { + icon: SolidThumbUpIcon, + color: 'text-green-500', + }, + }, + { + text: 'Zatwierdzony', + value: 'approved', + outline: { + icon: OutlineCheckIcon, + foreground: 'text-white', + background: 'bg-blumilk-500', + }, + solid: { + icon: SolidCheckIcon, + color: 'text-blumilk-500', + }, + }, + { + text: 'Anulowany', + value: 'canceled', + outline: { + icon: OutlineXIcon, + foreground: 'text-white', + background: 'bg-gray-900', + }, + solid: { + icon: SolidXIcon, + color: 'text-gray-900', + }, + }, +] + +export function useStatusInfo(status) { + return statuses.find(statusInfo => statusInfo.value === status) +} diff --git a/resources/js/Pages/Calendar.vue b/resources/js/Pages/Calendar.vue new file mode 100644 index 0000000..1415b0a --- /dev/null +++ b/resources/js/Pages/Calendar.vue @@ -0,0 +1,167 @@ + + + diff --git a/resources/js/Pages/Dashboard.vue b/resources/js/Pages/Dashboard.vue index 3232ab0..9051660 100644 --- a/resources/js/Pages/Dashboard.vue +++ b/resources/js/Pages/Dashboard.vue @@ -37,7 +37,7 @@
View profile diff --git a/resources/js/Pages/Holidays/Create.vue b/resources/js/Pages/Holidays/Create.vue index 0746c1b..b906242 100644 --- a/resources/js/Pages/Holidays/Create.vue +++ b/resources/js/Pages/Holidays/Create.vue @@ -1,6 +1,6 @@