diff --git a/app/Domain/Enums/VacationType.php b/app/Domain/Enums/VacationType.php index 63e2858..71bdd41 100644 --- a/app/Domain/Enums/VacationType.php +++ b/app/Domain/Enums/VacationType.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Toby\Domain\Enums; +use Illuminate\Support\Collection; + enum VacationType: string { case Vacation = "vacation"; @@ -15,6 +17,7 @@ enum VacationType: string case Volunteering = "volunteering_vacation"; case TimeInLieu = "time_in_lieu"; case Sick = "sick_vacation"; + case Absence = "absence"; public function label(): string { @@ -23,7 +26,7 @@ enum VacationType: string public static function casesToSelect(): array { - $cases = collect(VacationType::cases()); + $cases = VacationType::all(); return $cases->map( fn(VacationType $enum) => [ @@ -32,4 +35,9 @@ enum VacationType: string ], )->toArray(); } + + public static function all(): Collection + { + return new Collection(VacationType::cases()); + } } diff --git a/app/Domain/TimesheetExport.php b/app/Domain/TimesheetExport.php index f0ed480..8d764d0 100644 --- a/app/Domain/TimesheetExport.php +++ b/app/Domain/TimesheetExport.php @@ -4,20 +4,21 @@ declare(strict_types=1); namespace Toby\Domain; -use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Carbon; +use Illuminate\Support\Collection; use Maatwebsite\Excel\Concerns\WithMultipleSheets; use Toby\Eloquent\Models\User; class TimesheetExport implements WithMultipleSheets { protected Collection $users; + protected Collection $types; protected Carbon $month; public function sheets(): array { return $this->users - ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month)) + ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month, $this->types)) ->toArray(); } @@ -34,4 +35,11 @@ class TimesheetExport implements WithMultipleSheets return $this; } + + public function forVacationTypes(Collection $types): static + { + $this->types = $types; + + return $this; + } } diff --git a/app/Domain/TimesheetPerUserSheet.php b/app/Domain/TimesheetPerUserSheet.php index e419463..48098cf 100644 --- a/app/Domain/TimesheetPerUserSheet.php +++ b/app/Domain/TimesheetPerUserSheet.php @@ -7,6 +7,7 @@ namespace Toby\Domain; use Carbon\CarbonInterface; use Carbon\CarbonPeriod; use Generator; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Maatwebsite\Excel\Concerns\FromGenerator; @@ -40,6 +41,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With public function __construct( protected User $user, protected Carbon $month, + protected Collection $types, ) {} public function title(): string @@ -49,8 +51,6 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With public function headings(): array { - $types = VacationType::cases(); - $headings = [ __("Date"), __("Day of week"), @@ -59,7 +59,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With __("Worked hours"), ]; - foreach ($types as $type) { + foreach ($this->types as $type) { $headings[] = $type->label(); } @@ -187,6 +187,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With { return $user->vacations() ->with("vacationRequest") + ->whereRelation("vacationRequest", fn(Builder $query) => $query->whereIn("type", $this->types)) ->whereBetween("date", [$period->start, $period->end]) ->approved() ->get() diff --git a/app/Domain/UserVacationStatsRetriever.php b/app/Domain/UserVacationStatsRetriever.php index dcf8c99..8d1e66d 100644 --- a/app/Domain/UserVacationStatsRetriever.php +++ b/app/Domain/UserVacationStatsRetriever.php @@ -95,14 +95,14 @@ class UserVacationStatsRetriever protected function getLimitableVacationTypes(): Collection { - $types = new Collection(VacationType::cases()); + $types = VacationType::all(); return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); } protected function getNotLimitableVacationTypes(): Collection { - $types = new Collection(VacationType::cases()); + $types = VacationType::all(); return $types->filter(fn(VacationType $type) => !$this->configRetriever->hasLimit($type)); } diff --git a/app/Domain/VacationTypeConfigRetriever.php b/app/Domain/VacationTypeConfigRetriever.php index 47ed0ba..213fcc8 100644 --- a/app/Domain/VacationTypeConfigRetriever.php +++ b/app/Domain/VacationTypeConfigRetriever.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Toby\Domain; use Illuminate\Contracts\Config\Repository; +use Toby\Domain\Enums\EmploymentForm; use Toby\Domain\Enums\VacationType; class VacationTypeConfigRetriever @@ -13,6 +14,7 @@ class VacationTypeConfigRetriever public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval"; public const KEY_BILLABLE = "billable"; public const KEY_HAS_LIMIT = "has_limit"; + public const KEY_AVAILABLE_FOR = "available_for"; public function __construct( protected Repository $config, @@ -38,6 +40,11 @@ class VacationTypeConfigRetriever return $this->getConfigFor($type)[static::KEY_HAS_LIMIT]; } + public function isAvailableFor(VacationType $type, EmploymentForm $employmentForm): bool + { + return in_array($employmentForm, $this->getConfigFor($type)[static::KEY_AVAILABLE_FOR], true); + } + protected function getConfigFor(VacationType $type): array { return $this->config->get("vacation_types.{$type->value}"); diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index 453c343..c835cf6 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -5,7 +5,7 @@ declare(strict_types=1); namespace Toby\Domain\Validation\Rules; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Collection; use Toby\Domain\Enums\VacationType; use Toby\Domain\VacationDaysCalculator; use Toby\Domain\VacationRequestStatesRetriever; @@ -59,7 +59,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule protected function getLimitableVacationTypes(): Collection { - $types = new Collection(VacationType::cases()); + $types = VacationType::all(); return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); } diff --git a/app/Infrastructure/Http/Controllers/Api/GetAvailableVacationTypesController.php b/app/Infrastructure/Http/Controllers/Api/GetAvailableVacationTypesController.php new file mode 100644 index 0000000..20d923c --- /dev/null +++ b/app/Infrastructure/Http/Controllers/Api/GetAvailableVacationTypesController.php @@ -0,0 +1,33 @@ +find($request->get("user")); + + $types = VacationType::all() + ->filter(fn(VacationType $type) => $configRetriever->isAvailableFor($type, $user->employment_form)) + ->map(fn(VacationType $type) => [ + "label" => $type->label(), + "value" => $type->value, + ]) + ->values(); + + return new JsonResponse($types); + } +} diff --git a/app/Infrastructure/Http/Controllers/TimesheetController.php b/app/Infrastructure/Http/Controllers/TimesheetController.php index db26dfa..69cd971 100644 --- a/app/Infrastructure/Http/Controllers/TimesheetController.php +++ b/app/Infrastructure/Http/Controllers/TimesheetController.php @@ -7,30 +7,43 @@ namespace Toby\Infrastructure\Http\Controllers; use Illuminate\Support\Carbon; use Maatwebsite\Excel\Facades\Excel; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Toby\Domain\Enums\EmploymentForm; use Toby\Domain\Enums\Month; +use Toby\Domain\Enums\VacationType; use Toby\Domain\TimesheetExport; +use Toby\Domain\VacationTypeConfigRetriever; use Toby\Eloquent\Helpers\YearPeriodRetriever; use Toby\Eloquent\Models\User; class TimesheetController extends Controller { - public function __invoke(Month $month, YearPeriodRetriever $yearPeriodRetriever): BinaryFileResponse - { + public function __invoke( + Month $month, + YearPeriodRetriever $yearPeriodRetriever, + VacationTypeConfigRetriever $configRetriever, + ): BinaryFileResponse { $this->authorize("generateTimesheet"); $yearPeriod = $yearPeriodRetriever->selected(); $carbonMonth = Carbon::create($yearPeriod->year, $month->toCarbonNumber()); $users = User::query() + ->where("employment_form", EmploymentForm::EmploymentContract) ->orderBy("last_name") ->orderBy("first_name") ->get(); + $types = VacationType::all() + ->filter( + fn(VacationType $type) => $configRetriever->isAvailableFor($type, EmploymentForm::EmploymentContract), + ); + $filename = "{$carbonMonth->translatedFormat("F Y")}.xlsx"; $timesheet = (new TimesheetExport()) ->forMonth($carbonMonth) - ->forUsers($users); + ->forUsers($users) + ->forVacationTypes($types); return Excel::download($timesheet, $filename); } diff --git a/app/Infrastructure/Http/Controllers/VacationRequestController.php b/app/Infrastructure/Http/Controllers/VacationRequestController.php index 6d1cf6d..4f3134e 100644 --- a/app/Infrastructure/Http/Controllers/VacationRequestController.php +++ b/app/Infrastructure/Http/Controllers/VacationRequestController.php @@ -158,10 +158,7 @@ class VacationRequestController extends Controller public function create(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response { - $yearPeriod = $yearPeriodRetriever->selected(); - $users = User::query() - ->withVacationLimitIn($yearPeriod) ->orderBy("last_name") ->orderBy("first_name") ->get(); diff --git a/app/Infrastructure/Http/Requests/Api/GetAvailableVacationTypesRequest.php b/app/Infrastructure/Http/Requests/Api/GetAvailableVacationTypesRequest.php new file mode 100644 index 0000000..35f4bad --- /dev/null +++ b/app/Infrastructure/Http/Requests/Api/GetAvailableVacationTypesRequest.php @@ -0,0 +1,17 @@ + ["required", "exists:users,id"], + ]; + } +} diff --git a/config/vacation_types.php b/config/vacation_types.php index 45a5900..e9b4402 100644 --- a/config/vacation_types.php +++ b/config/vacation_types.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Toby\Domain\Enums\EmploymentForm; use Toby\Domain\Enums\VacationType; use Toby\Domain\VacationTypeConfigRetriever; @@ -11,53 +12,100 @@ return [ VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => true, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::OnRequest->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => true, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::TimeInLieu->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Sick->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => false, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Unpaid->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Special->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Childcare->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => false, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Training->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], ], VacationType::Volunteering->value => [ VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, VacationTypeConfigRetriever::KEY_BILLABLE => true, VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], + ], + VacationType::Volunteering->value => [ + VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, + VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => true, + VacationTypeConfigRetriever::KEY_BILLABLE => true, + VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::EmploymentContract, + ], + ], + VacationType::Absence->value => [ + VacationTypeConfigRetriever::KEY_TECHNICAL_APPROVAL => true, + VacationTypeConfigRetriever::KEY_ADMINISTRATIVE_APPROVAL => false, + VacationTypeConfigRetriever::KEY_BILLABLE => false, + VacationTypeConfigRetriever::KEY_HAS_LIMIT => false, + VacationTypeConfigRetriever::KEY_AVAILABLE_FOR => [ + EmploymentForm::CommissionContract, + EmploymentForm::B2bContract, + EmploymentForm::BoardMemberContract, + ], ], ]; diff --git a/resources/js/Composables/vacationTypeInfo.js b/resources/js/Composables/vacationTypeInfo.js index 6594ab7..650c14e 100644 --- a/resources/js/Composables/vacationTypeInfo.js +++ b/resources/js/Composables/vacationTypeInfo.js @@ -7,6 +7,7 @@ import CurrencyUsdOffIcon from 'vue-material-design-icons/CurrencyUsdOff.vue' import HandHeartOutlineIcon from 'vue-material-design-icons/HandHeartOutline.vue' import CalendarCheckIcon from 'vue-material-design-icons/CalendarCheck.vue' import MedicalBagIcon from 'vue-material-design-icons/MedicalBag.vue' +import CalendarRemoveIcon from 'vue-material-design-icons/CalendarRemove.vue' const types = [ { @@ -63,6 +64,12 @@ const types = [ icon: MedicalBagIcon, color: 'text-rose-500', }, + { + text: 'Nieobecność', + value: 'absence', + icon: CalendarRemoveIcon, + color: 'text-cyan-500', + }, ] export default function useVacationTypeInfo() { diff --git a/resources/js/Pages/MonthlyUsage.vue b/resources/js/Pages/MonthlyUsage.vue index 3ef5e1d..0db17f2 100644 --- a/resources/js/Pages/MonthlyUsage.vue +++ b/resources/js/Pages/MonthlyUsage.vue @@ -4,7 +4,7 @@