Compare commits
	
		
			63 Commits
		
	
	
		
			#17-dusk-t
			...
			26220f1025
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 26220f1025 | |||
| e39827df20 | |||
| b64f2ce269 | |||
| 600a1a369f | |||
|  | 902af64833 | ||
|  | d1047e8262 | ||
| 643f546142 | |||
| 995c0b6696 | |||
| 9147b859d3 | |||
|  | 2f9ef0ff12 | ||
|  | 68e32ad930 | ||
|  | 31a6d287c8 | ||
| 77d3b4df0d | |||
| 2fb2415203 | |||
| fe639f264d | |||
|  | 4b06e6c02b | ||
| 4309e8104b | |||
|  | 3af92b2085 | ||
|  | 0bebe2ecf1 | ||
|  | a3b8b18384 | ||
|  | 431262dfb7 | ||
|  | 7154caa340 | ||
|  | 74f33d6c0b | ||
|  | 88b1543071 | ||
|  | f4d928c6ae | ||
|  | 497f47068c | ||
|  | e1f449fb52 | ||
|  | 3404bf1da8 | ||
|  | 94da727fe4 | ||
|  | 06863b854a | ||
|  | 6b2556c1da | ||
|  | c69866bb52 | ||
|  | d60dc75f99 | ||
|  | c95d08c861 | ||
|  | cc981b02b4 | ||
|  | 459b62500e | ||
|  | 4af0482a93 | ||
|  | ff8d6aade6 | ||
|  | 84403a762a | ||
|  | 6af4380fe6 | ||
|  | fa244b96cd | ||
|  | 3ab02f1df4 | ||
|  | 1ae23bd7cb | ||
|  | b52d206554 | ||
|  | 172eab162d | ||
|  | 794e8df3ea | ||
|  | b49fcadbba | ||
|  | 08421b8a69 | ||
|  | ab16af1ca9 | ||
|  | fdbc374d7e | ||
|  | 720d2c4e7b | ||
|  | 957b07b3eb | ||
|  | dcda8c6255 | ||
|  | 95f5ed44d6 | ||
|  | d8ac2bd61f | ||
|  | a0e60a3160 | ||
|  | 8c1819aa01 | ||
|  | afb1a5e9ff | ||
|  | 43870fa060 | ||
|  | 0076c04e88 | ||
|  | 287c6c19ab | ||
|  | 8a54403318 | ||
|  | 6d62c8b776 | 
							
								
								
									
										13
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								.env.example
									
									
									
									
									
								
							| @@ -57,6 +57,19 @@ DOCKER_INSTALL_XDEBUG=false | |||||||
|  |  | ||||||
| GOOGLE_CLIENT_ID= | GOOGLE_CLIENT_ID= | ||||||
| GOOGLE_CLIENT_SECRET= | GOOGLE_CLIENT_SECRET= | ||||||
|  | GOOGLE_CALENDAR_ENABLED=true | ||||||
| GOOGLE_REDIRECT=http://localhost/login/google/end | GOOGLE_REDIRECT=http://localhost/login/google/end | ||||||
| GOOGLE_CALENDAR_ID= | GOOGLE_CALENDAR_ID= | ||||||
| LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= | LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= | ||||||
|  |  | ||||||
|  | SLACK_URL=https://slack.com/api | ||||||
|  | SLACK_ENABLED=true | ||||||
|  | SLACK_CLIENT_TOKEN= | ||||||
|  | SLACK_SIGNING_SECRET= | ||||||
|  | SLACK_DEFAULT_CHANNEL="#general" | ||||||
|  |  | ||||||
|  | HEROKU_RELEASE_VERSION=v01 | ||||||
|  | HEROKU_SLUG_DESCRIPTION="Dev 643f546" | ||||||
|  | HEROKU_RELEASE_CREATED_AT=2022-07-04T18:05:46Z | ||||||
|  | HEROKU_SLUG_COMMIT=643f546142b8a2db342f0ae46f01e61dce03e97c | ||||||
|  | GITHUB_REPO_URL=https://github.com/blumilksoftware/toby/ | ||||||
|   | |||||||
| @@ -13,5 +13,8 @@ module.exports = { | |||||||
|     indent: ['error', 2], |     indent: ['error', 2], | ||||||
|     'vue/html-indent': ['error', 2], |     'vue/html-indent': ['error', 2], | ||||||
|     'comma-dangle': ['error', 'always-multiline'], |     'comma-dangle': ['error', 'always-multiline'], | ||||||
|  |     'object-curly-spacing': ['error', 'always'], | ||||||
|  |     'vue/require-default-prop': 0, | ||||||
|  |     'vue/multi-word-component-names': 0, | ||||||
|   }, |   }, | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | * @blumilksoftware/toby | ||||||
							
								
								
									
										17
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.github/workflows/deploy.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | name: Deploy | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   push: | ||||||
|  |     tags: | ||||||
|  |       - v* | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   deploy: | ||||||
|  |     runs-on: ubuntu-20.04 | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v2 | ||||||
|  |       - uses: akhileshns/heroku-deploy@v3.12.12 | ||||||
|  |         with: | ||||||
|  |           heroku_api_key: ${{secrets.HEROKU_API_KEY}} | ||||||
|  |           heroku_app_name: ${{secrets.HEROKU_APP_NAME}} | ||||||
|  |           heroku_email: ${{secrets.HEROKU_EMAIL}} | ||||||
							
								
								
									
										2
									
								
								.github/workflows/test-and-lint-php.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/test-and-lint-php.yml
									
									
									
									
										vendored
									
									
								
							| @@ -45,7 +45,7 @@ jobs: | |||||||
|         run: composer install --prefer-dist --no-interaction --no-suggest |         run: composer install --prefer-dist --no-interaction --no-suggest | ||||||
|  |  | ||||||
|       - name: Run PHP linter |       - name: Run PHP linter | ||||||
|         run: composer ecs |         run: composer cs | ||||||
|  |  | ||||||
|       - name: Execute tests |       - name: Execute tests | ||||||
|         run: php artisan test --env=ci |         run: php artisan test --env=ci | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -15,5 +15,6 @@ Homestead.json | |||||||
| Homestead.yaml | Homestead.yaml | ||||||
| npm-debug.log | npm-debug.log | ||||||
| yarn-error.log | yarn-error.log | ||||||
|  | google-credentials.json | ||||||
| .idea/ | .idea/ | ||||||
| .composer | .composer | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								Procfile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Procfile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | web: vendor/bin/heroku-php-nginx -C environment/prod/nginx.conf public/ | ||||||
|  | release: php artisan config:cache && php artisan route:cache && php artisan migrate --force | ||||||
|  | worker: php artisan queue:work | ||||||
| @@ -5,6 +5,9 @@ declare(strict_types=1); | |||||||
| namespace Toby\Architecture; | namespace Toby\Architecture; | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Exceptions\Handler; | use Illuminate\Foundation\Exceptions\Handler; | ||||||
|  | use Inertia\Inertia; | ||||||
|  | use Symfony\Component\HttpFoundation\Response; | ||||||
|  | use Throwable; | ||||||
|  |  | ||||||
| class ExceptionHandler extends Handler | class ExceptionHandler extends Handler | ||||||
| { | { | ||||||
| @@ -13,4 +16,36 @@ class ExceptionHandler extends Handler | |||||||
|         "password", |         "password", | ||||||
|         "password_confirmation", |         "password_confirmation", | ||||||
|     ]; |     ]; | ||||||
|  |     protected array $handleByInertia = [ | ||||||
|  |         Response::HTTP_INTERNAL_SERVER_ERROR, | ||||||
|  |         Response::HTTP_SERVICE_UNAVAILABLE, | ||||||
|  |         Response::HTTP_TOO_MANY_REQUESTS, | ||||||
|  |         419, // CSRF | ||||||
|  |         Response::HTTP_NOT_FOUND, | ||||||
|  |         Response::HTTP_FORBIDDEN, | ||||||
|  |         Response::HTTP_UNAUTHORIZED, | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     public function render($request, Throwable $e): Response | ||||||
|  |     { | ||||||
|  |         $response = parent::render($request, $e); | ||||||
|  |  | ||||||
|  |         if (!app()->environment("production")) { | ||||||
|  |             return $response; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($response->status() === Response::HTTP_METHOD_NOT_ALLOWED) { | ||||||
|  |             $response->setStatusCode(Response::HTTP_NOT_FOUND); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (in_array($response->status(), $this->handleByInertia, true)) { | ||||||
|  |             return Inertia::render("Error", [ | ||||||
|  |                 "status" => $response->status(), | ||||||
|  |             ]) | ||||||
|  |                 ->toResponse($request) | ||||||
|  |                 ->setStatusCode($response->status()); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $response; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,13 +4,24 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Architecture\Providers; | namespace Toby\Architecture\Providers; | ||||||
|  |  | ||||||
|  | use Illuminate\Contracts\Foundation\Application; | ||||||
|  | use Illuminate\Notifications\ChannelManager; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Facades\Notification; | ||||||
| use Illuminate\Support\ServiceProvider; | use Illuminate\Support\ServiceProvider; | ||||||
|  | use Toby\Infrastructure\Slack\Channels\SlackApiChannel; | ||||||
|  |  | ||||||
| class AppServiceProvider extends ServiceProvider | class AppServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|  |     public function register(): void | ||||||
|  |     { | ||||||
|  |         Notification::resolved(function (ChannelManager $service): void { | ||||||
|  |             $service->extend("slack", fn(Application $app): SlackApiChannel => $app->make(SlackApiChannel::class)); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
|     { |     { | ||||||
|         Carbon::macro("toDisplayString", fn() => $this->translatedFormat("d.m.Y")); |         Carbon::macro("toDisplayString", fn(): string => $this->translatedFormat("d.m.Y")); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,7 +7,9 @@ namespace Toby\Architecture\Providers; | |||||||
| use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; | use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; | ||||||
| use Illuminate\Support\Facades\Gate; | use Illuminate\Support\Facades\Gate; | ||||||
| use Toby\Domain\Enums\Role; | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Policies\KeyPolicy; | ||||||
| use Toby\Domain\Policies\VacationRequestPolicy; | use Toby\Domain\Policies\VacationRequestPolicy; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| @@ -15,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider | |||||||
| { | { | ||||||
|     protected $policies = [ |     protected $policies = [ | ||||||
|         VacationRequest::class => VacationRequestPolicy::class, |         VacationRequest::class => VacationRequestPolicy::class, | ||||||
|  |         Key::class => KeyPolicy::class, | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
| @@ -27,9 +30,11 @@ class AuthServiceProvider extends ServiceProvider | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         Gate::define("manageUsers", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageUsers", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("manageHolidays", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageHolidays", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("manageVacationLimits", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("manageVacationLimits", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|         Gate::define("generateTimesheet", fn(User $user) => $user->role === Role::AdministrativeApprover); |         Gate::define("generateTimesheet", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|  |         Gate::define("listMonthlyUsage", fn(User $user): bool => $user->role === Role::AdministrativeApprover); | ||||||
|  |         Gate::define("manageResumes", fn(User $user): bool => $user->role === Role::TechnicalApprover); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,39 +5,8 @@ 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\VacationRequestCancelled; |  | ||||||
| use Toby\Domain\Events\VacationRequestCreated; |  | ||||||
| use Toby\Domain\Events\VacationRequestRejected; |  | ||||||
| use Toby\Domain\Events\VacationRequestStateChanged; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForAdminApproval; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForTechApproval; |  | ||||||
| use Toby\Domain\Listeners\CreateVacationRequestActivity; |  | ||||||
| use Toby\Domain\Listeners\HandleAcceptedByAdministrativeVacationRequest; |  | ||||||
| use Toby\Domain\Listeners\HandleAcceptedByTechnicalVacationRequest; |  | ||||||
| use Toby\Domain\Listeners\HandleApprovedVacationRequest; |  | ||||||
| use Toby\Domain\Listeners\HandleCancelledVacationRequest; |  | ||||||
| use Toby\Domain\Listeners\HandleCreatedVacationRequest; |  | ||||||
| use Toby\Domain\Listeners\SendApprovedVacationRequestNotification; |  | ||||||
| use Toby\Domain\Listeners\SendCancelledVacationRequestNotification; |  | ||||||
| use Toby\Domain\Listeners\SendCreatedVacationRequestNotification; |  | ||||||
| use Toby\Domain\Listeners\SendRejectedVacationRequestNotification; |  | ||||||
| use Toby\Domain\Listeners\SendWaitedForAdministrativeVacationRequestNotification; |  | ||||||
| use Toby\Domain\Listeners\SendWaitedForTechnicalVacationRequestNotification; |  | ||||||
|  |  | ||||||
| class EventServiceProvider extends ServiceProvider | class EventServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|     protected $listen = [ |     protected $listen = []; | ||||||
|         VacationRequestStateChanged::class => [CreateVacationRequestActivity::class], |  | ||||||
|         VacationRequestCreated::class => [HandleCreatedVacationRequest::class, SendCreatedVacationRequestNotification::class], |  | ||||||
|         VacationRequestAcceptedByTechnical::class => [HandleAcceptedByTechnicalVacationRequest::class], |  | ||||||
|         VacationRequestAcceptedByAdministrative::class => [HandleAcceptedByAdministrativeVacationRequest::class], |  | ||||||
|         VacationRequestApproved::class => [HandleApprovedVacationRequest::class, SendApprovedVacationRequestNotification::class], |  | ||||||
|         VacationRequestRejected::class => [SendRejectedVacationRequestNotification::class], |  | ||||||
|         VacationRequestCancelled::class => [HandleCancelledVacationRequest::class, SendCancelledVacationRequestNotification::class], |  | ||||||
|         VacationRequestWaitsForTechApproval::class => [SendWaitedForTechnicalVacationRequestNotification::class], |  | ||||||
|         VacationRequestWaitsForAdminApproval::class => [SendWaitedForAdministrativeVacationRequestNotification::class], |  | ||||||
|     ]; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,17 +7,14 @@ 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\VacationRequest; | ||||||
| use Toby\Eloquent\Models\YearPeriod; |  | ||||||
| use Toby\Eloquent\Observers\UserObserver; | use Toby\Eloquent\Observers\UserObserver; | ||||||
| use Toby\Eloquent\Observers\VacationRequestObserver; | use Toby\Eloquent\Observers\VacationRequestObserver; | ||||||
| use Toby\Eloquent\Observers\YearPeriodObserver; |  | ||||||
|  |  | ||||||
| class ObserverServiceProvider extends ServiceProvider | class ObserverServiceProvider extends ServiceProvider | ||||||
| { | { | ||||||
|     public function boot(): void |     public function boot(): void | ||||||
|     { |     { | ||||||
|         User::observe(UserObserver::class); |         User::observe(UserObserver::class); | ||||||
|         YearPeriod::observe(YearPeriodObserver::class); |  | ||||||
|         VacationRequest::observe(VacationRequestObserver::class); |         VacationRequest::observe(VacationRequestObserver::class); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -28,6 +28,6 @@ class RouteServiceProvider extends ServiceProvider | |||||||
|  |  | ||||||
|     protected function configureRateLimiting(): void |     protected function configureRateLimiting(): void | ||||||
|     { |     { | ||||||
|         RateLimiter::for("api", fn(Request $request) => Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip())); |         RateLimiter::for("api", fn(Request $request): Limit => Limit::perMinute(60)->by(optional($request->user())->id ?: $request->ip())); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								app/Domain/Actions/CreateUserAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Domain/Actions/CreateUserAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions; | ||||||
|  |  | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  |  | ||||||
|  | class CreateUserAction | ||||||
|  | { | ||||||
|  |     public function execute(array $userData, array $profileData): User | ||||||
|  |     { | ||||||
|  |         $user = new User($userData); | ||||||
|  |  | ||||||
|  |         $user->save(); | ||||||
|  |  | ||||||
|  |         $user->profile()->create($profileData); | ||||||
|  |  | ||||||
|  |         $this->createVacationLimitsFor($user); | ||||||
|  |  | ||||||
|  |         return $user; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function createVacationLimitsFor(User $user): void | ||||||
|  |     { | ||||||
|  |         $yearPeriods = YearPeriod::all(); | ||||||
|  |  | ||||||
|  |         foreach ($yearPeriods as $yearPeriod) { | ||||||
|  |             $user->vacationLimits()->create([ | ||||||
|  |                 "year_period_id" => $yearPeriod->id, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,22 +2,30 @@ | |||||||
| 
 | 
 | ||||||
| declare(strict_types=1); | declare(strict_types=1); | ||||||
| 
 | 
 | ||||||
| namespace Toby\Eloquent\Observers; | namespace Toby\Domain\Actions; | ||||||
| 
 | 
 | ||||||
| use Toby\Domain\PolishHolidaysRetriever; | use Toby\Domain\PolishHolidaysRetriever; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| 
 | 
 | ||||||
| class YearPeriodObserver | class CreateYearPeriodAction | ||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected PolishHolidaysRetriever $polishHolidaysRetriever, |         protected PolishHolidaysRetriever $polishHolidaysRetriever, | ||||||
|     ) {} |     ) {} | ||||||
| 
 | 
 | ||||||
|     public function created(YearPeriod $yearPeriod): void |     public function execute(int $year): YearPeriod | ||||||
|     { |     { | ||||||
|  |         $yearPeriod = new YearPeriod([ | ||||||
|  |             "year" => $year, | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         $yearPeriod->save(); | ||||||
|  | 
 | ||||||
|         $this->createVacationLimitsFor($yearPeriod); |         $this->createVacationLimitsFor($yearPeriod); | ||||||
|         $this->createHolidaysFor($yearPeriod); |         $this->createHolidaysFor($yearPeriod); | ||||||
|  | 
 | ||||||
|  |         return $yearPeriod; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     protected function createVacationLimitsFor(YearPeriod $yearPeriod): void |     protected function createVacationLimitsFor(YearPeriod $yearPeriod): void | ||||||
							
								
								
									
										19
									
								
								app/Domain/Actions/UpdateUserAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Domain/Actions/UpdateUserAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions; | ||||||
|  |  | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class UpdateUserAction | ||||||
|  | { | ||||||
|  |     public function execute(User $user, array $userData, array $profileData): User | ||||||
|  |     { | ||||||
|  |         $user->update($userData); | ||||||
|  |  | ||||||
|  |         $user->profile->update($profileData); | ||||||
|  |  | ||||||
|  |         return $user; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class AcceptAsAdministrativeAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected ApproveAction $approveAction, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest, User $user): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->acceptAsAdministrative($vacationRequest, $user); | ||||||
|  |  | ||||||
|  |         $this->approveAction->execute($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class AcceptAsTechnicalAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected WaitForAdminApprovalAction $waitForAdminApprovalAction, | ||||||
|  |         protected ApproveAction $approveAction, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest, User $user): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->acceptAsTechnical($vacationRequest, $user); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) { | ||||||
|  |             $this->waitForAdminApprovalAction->execute($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $this->approveAction->execute($vacationRequest); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								app/Domain/Actions/VacationRequest/ApproveAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Domain/Actions/VacationRequest/ApproveAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestStatusChangedNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar; | ||||||
|  |  | ||||||
|  | class ApproveAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest, ?User $user = null): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->approve($vacationRequest, $user); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->isVacation($vacationRequest->type)) { | ||||||
|  |             SendVacationRequestDaysToGoogleCalendar::dispatch($vacationRequest); | ||||||
|  |  | ||||||
|  |             $this->notify($vacationRequest); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notify(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->where("id", "!=", $vacationRequest->user->id) | ||||||
|  |             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								app/Domain/Actions/VacationRequest/CancelAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								app/Domain/Actions/VacationRequest/CancelAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestStatusChangedNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Jobs\ClearVacationRequestDaysInGoogleCalendar; | ||||||
|  |  | ||||||
|  | class CancelAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest, User $user): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->cancel($vacationRequest, $user); | ||||||
|  |  | ||||||
|  |         ClearVacationRequestDaysInGoogleCalendar::dispatch($vacationRequest); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->isVacation($vacationRequest->type)) { | ||||||
|  |             $this->notify($vacationRequest); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notify(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->where("id", "!=", $vacationRequest->user->id) | ||||||
|  |             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										97
									
								
								app/Domain/Actions/VacationRequest/CreateAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								app/Domain/Actions/VacationRequest/CreateAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Illuminate\Validation\ValidationException; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestCreatedNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Domain\Validation\VacationRequestValidator; | ||||||
|  | use Toby\Domain\WorkDaysCalculator; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class CreateAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationRequestValidator $vacationRequestValidator, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|  |         protected WaitForTechApprovalAction $waitForTechApprovalAction, | ||||||
|  |         protected WaitForAdminApprovalAction $waitForAdminApprovalAction, | ||||||
|  |         protected ApproveAction $approveAction, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws ValidationException | ||||||
|  |      */ | ||||||
|  |     public function execute(array $data, User $creator): VacationRequest | ||||||
|  |     { | ||||||
|  |         $vacationRequest = $this->createVacationRequest($data, $creator); | ||||||
|  |         $this->handleCreatedVacationRequest($vacationRequest); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->isVacation($vacationRequest->type)) { | ||||||
|  |             $this->notify($vacationRequest); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $vacationRequest; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @throws ValidationException | ||||||
|  |      */ | ||||||
|  |     protected function createVacationRequest(array $data, User $creator): VacationRequest | ||||||
|  |     { | ||||||
|  |         /** @var VacationRequest $vacationRequest */ | ||||||
|  |         $vacationRequest = $creator->createdVacationRequests()->make($data); | ||||||
|  |  | ||||||
|  |         $this->vacationRequestValidator->validate($vacationRequest); | ||||||
|  |  | ||||||
|  |         $vacationRequest->save(); | ||||||
|  |  | ||||||
|  |         $days = $this->workDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); | ||||||
|  |  | ||||||
|  |         foreach ($days as $day) { | ||||||
|  |             $vacationRequest->vacations()->create([ | ||||||
|  |                 "date" => $day, | ||||||
|  |                 "user_id" => $vacationRequest->user->id, | ||||||
|  |                 "year_period_id" => $vacationRequest->yearPeriod->id, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $this->stateManager->markAsCreated($vacationRequest); | ||||||
|  |  | ||||||
|  |         return $vacationRequest; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function handleCreatedVacationRequest(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         if ($vacationRequest->hasFlowSkipped()) { | ||||||
|  |             $this->approveAction->execute($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsTechnicalApproval($vacationRequest->type)) { | ||||||
|  |             $this->waitForTechApprovalAction->execute($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->needsAdministrativeApproval($vacationRequest->type)) { | ||||||
|  |             $this->waitForAdminApprovalAction->execute($vacationRequest); | ||||||
|  |  | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $this->stateManager->approve($vacationRequest); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notify(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $vacationRequest->user->notify(new VacationRequestCreatedNotification($vacationRequest)); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										39
									
								
								app/Domain/Actions/VacationRequest/RejectAction.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/Domain/Actions/VacationRequest/RejectAction.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestStatusChangedNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class RejectAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest, User $user): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->reject($vacationRequest, $user); | ||||||
|  |  | ||||||
|  |         $this->notify($vacationRequest); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notify(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->where("id", "!=", $vacationRequest->user->id) | ||||||
|  |             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $user)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $vacationRequest->user->notify(new VacationRequestStatusChangedNotification($vacationRequest, $vacationRequest->user)); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class WaitForAdminApprovalAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected ApproveAction $approveAction, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->waitForAdministrative($vacationRequest); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->isVacation($vacationRequest->type)) { | ||||||
|  |             $this->notifyAdminApprovers($vacationRequest); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notifyAdminApprovers(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->whereIn("role", [Role::AdministrativeApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Actions\VacationRequest; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification; | ||||||
|  | use Toby\Domain\VacationRequestStateManager; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class WaitForTechApprovalAction | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationRequestStateManager $stateManager, | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |         protected ApproveAction $approveAction, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function execute(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $this->stateManager->waitForTechnical($vacationRequest); | ||||||
|  |  | ||||||
|  |         if ($this->configRetriever->isVacation($vacationRequest->type)) { | ||||||
|  |             $this->notifyTechApprovers($vacationRequest); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function notifyTechApprovers(VacationRequest $vacationRequest): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->whereIn("role", [Role::TechnicalApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,7 +5,6 @@ declare(strict_types=1); | |||||||
| namespace Toby\Domain; | namespace Toby\Domain; | ||||||
|  |  | ||||||
| use Carbon\CarbonPeriod; | use Carbon\CarbonPeriod; | ||||||
| use Illuminate\Database\Eloquent\Builder; |  | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; | use Toby\Eloquent\Helpers\YearPeriodRetriever; | ||||||
| @@ -44,6 +43,7 @@ class CalendarGenerator | |||||||
|                 "isWeekend" => $day->isWeekend(), |                 "isWeekend" => $day->isWeekend(), | ||||||
|                 "isHoliday" => $holidays->contains($day), |                 "isHoliday" => $holidays->contains($day), | ||||||
|                 "vacations" => $vacationsForDay->pluck("user_id"), |                 "vacations" => $vacationsForDay->pluck("user_id"), | ||||||
|  |                 "vacationTypes" => $vacationsForDay->pluck("vacationRequest.type", "user_id"), | ||||||
|             ]; |             ]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -54,8 +54,9 @@ class CalendarGenerator | |||||||
|     { |     { | ||||||
|         return Vacation::query() |         return Vacation::query() | ||||||
|             ->whereBetween("date", [$period->start, $period->end]) |             ->whereBetween("date", [$period->start, $period->end]) | ||||||
|             ->whereRelation("vacationRequest", fn(Builder $query) => $query->states(VacationRequestStatesRetriever::successStates())) |             ->approved() | ||||||
|  |             ->with("vacationRequest") | ||||||
|             ->get() |             ->get() | ||||||
|             ->groupBy(fn(Vacation $vacation) => $vacation->date->toDateString()); |             ->groupBy(fn(Vacation $vacation): string => $vacation->date->toDateString()); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										49
									
								
								app/Domain/DailySummaryRetriever.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								app/Domain/DailySummaryRetriever.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
|  |  | ||||||
|  | class DailySummaryRetriever | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function getAbsences(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return Vacation::query() | ||||||
|  |             ->with(["user", "vacationRequest"]) | ||||||
|  |             ->whereDate("date", $date) | ||||||
|  |             ->approved() | ||||||
|  |             ->whereTypes( | ||||||
|  |                 VacationType::all()->filter(fn(VacationType $type): bool => $this->configRetriever->isVacation($type)), | ||||||
|  |             ) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getRemoteDays(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return Vacation::query() | ||||||
|  |             ->with(["user", "vacationRequest"]) | ||||||
|  |             ->whereDate("date", $date) | ||||||
|  |             ->approved() | ||||||
|  |             ->whereTypes( | ||||||
|  |                 VacationType::all()->filter(fn(VacationType $type): bool => !$this->configRetriever->isVacation($type)), | ||||||
|  |             ) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getBirthdays(Carbon $date): Collection | ||||||
|  |     { | ||||||
|  |         return User::query() | ||||||
|  |             ->whereRelation("profile", "birthday", $date) | ||||||
|  |             ->get(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -21,7 +21,7 @@ enum EmploymentForm: string | |||||||
|         $cases = collect(EmploymentForm::cases()); |         $cases = collect(EmploymentForm::cases()); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(EmploymentForm $enum) => [ |             fn(EmploymentForm $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ enum Role: string | |||||||
|         $cases = collect(Role::cases()); |         $cases = collect(Role::cases()); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(Role $enum) => [ |             fn(Role $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Enums; | namespace Toby\Domain\Enums; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  |  | ||||||
| enum VacationType: string | enum VacationType: string | ||||||
| { | { | ||||||
|     case Vacation = "vacation"; |     case Vacation = "vacation"; | ||||||
| @@ -15,6 +17,8 @@ enum VacationType: string | |||||||
|     case Volunteering = "volunteering_vacation"; |     case Volunteering = "volunteering_vacation"; | ||||||
|     case TimeInLieu = "time_in_lieu"; |     case TimeInLieu = "time_in_lieu"; | ||||||
|     case Sick = "sick_vacation"; |     case Sick = "sick_vacation"; | ||||||
|  |     case Absence = "absence"; | ||||||
|  |     case HomeOffice = "home_office"; | ||||||
|  |  | ||||||
|     public function label(): string |     public function label(): string | ||||||
|     { |     { | ||||||
| @@ -23,13 +27,18 @@ enum VacationType: string | |||||||
|  |  | ||||||
|     public static function casesToSelect(): array |     public static function casesToSelect(): array | ||||||
|     { |     { | ||||||
|         $cases = collect(VacationType::cases()); |         $cases = VacationType::all(); | ||||||
|  |  | ||||||
|         return $cases->map( |         return $cases->map( | ||||||
|             fn(VacationType $enum) => [ |             fn(VacationType $enum): array => [ | ||||||
|                 "label" => $enum->label(), |                 "label" => $enum->label(), | ||||||
|                 "value" => $enum->value, |                 "value" => $enum->value, | ||||||
|             ], |             ], | ||||||
|         )->toArray(); |         )->toArray(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static function all(): Collection | ||||||
|  |     { | ||||||
|  |         return new Collection(VacationType::cases()); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?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, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?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, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?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, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Events; |  | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Events\Dispatchable; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestCancelled |  | ||||||
| { |  | ||||||
|     use Dispatchable; |  | ||||||
|     use SerializesModels; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         public VacationRequest $vacationRequest, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?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, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Events; |  | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Events\Dispatchable; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestRejected |  | ||||||
| { |  | ||||||
|     use Dispatchable; |  | ||||||
|     use SerializesModels; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         public VacationRequest $vacationRequest, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,24 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Events; |  | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Events\Dispatchable; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| use Toby\Domain\States\VacationRequest\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, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Events; |  | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Events\Dispatchable; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestWaitsForAdminApproval |  | ||||||
| { |  | ||||||
|     use Dispatchable; |  | ||||||
|     use SerializesModels; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         public VacationRequest $vacationRequest, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Events; |  | ||||||
|  |  | ||||||
| use Illuminate\Foundation\Events\Dispatchable; |  | ||||||
| use Illuminate\Queue\SerializesModels; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestWaitsForTechApproval |  | ||||||
| { |  | ||||||
|     use Dispatchable; |  | ||||||
|     use SerializesModels; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         public VacationRequest $vacationRequest, |  | ||||||
|     ) {} |  | ||||||
| } |  | ||||||
| @@ -1,19 +0,0 @@ | |||||||
| <?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, |  | ||||||
|         ]); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,20 +0,0 @@ | |||||||
| <?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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| <?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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Toby\Domain\Events\VacationRequestApproved; |  | ||||||
| use Toby\Infrastructure\Jobs\SendVacationRequestDaysToGoogleCalendar; |  | ||||||
|  |  | ||||||
| class HandleApprovedVacationRequest |  | ||||||
| { |  | ||||||
|     public function handle(VacationRequestApproved $event): void |  | ||||||
|     { |  | ||||||
|         SendVacationRequestDaysToGoogleCalendar::dispatch($event->vacationRequest); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Toby\Domain\Events\VacationRequestCancelled; |  | ||||||
| use Toby\Infrastructure\Jobs\ClearVacationRequestDaysInGoogleCalendar; |  | ||||||
|  |  | ||||||
| class HandleCancelledVacationRequest |  | ||||||
| { |  | ||||||
|     public function handle(VacationRequestCancelled $event): void |  | ||||||
|     { |  | ||||||
|         ClearVacationRequestDaysInGoogleCalendar::dispatch($event->vacationRequest); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,42 +0,0 @@ | |||||||
| <?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 ($vacationRequest->hasFlowSkipped()) { |  | ||||||
|             $this->stateManager->approve($vacationRequest); |  | ||||||
|  |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         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); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Toby\Domain\Enums\Role; |  | ||||||
| use Toby\Domain\Events\VacationRequestApproved; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestApprovedNotification; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class SendApprovedVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestApproved $event): void |  | ||||||
|     { |  | ||||||
|         foreach ($this->getUsersForNotifications() as $user) { |  | ||||||
|             $user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $user)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $event->vacationRequest->user->notify(new VacationRequestApprovedNotification($event->vacationRequest, $event->vacationRequest->user)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function getUsersForNotifications(): Collection |  | ||||||
|     { |  | ||||||
|         return User::query() |  | ||||||
|             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) |  | ||||||
|             ->get(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Toby\Domain\Enums\Role; |  | ||||||
| use Toby\Domain\Events\VacationRequestCancelled; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestCancelledNotification; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class SendCancelledVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestCancelled $event): void |  | ||||||
|     { |  | ||||||
|         foreach ($this->getUsersForNotifications() as $user) { |  | ||||||
|             $user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $user)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $event->vacationRequest->user->notify(new VacationRequestCancelledNotification($event->vacationRequest, $event->vacationRequest->user)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function getUsersForNotifications(): Collection |  | ||||||
|     { |  | ||||||
|         return User::query() |  | ||||||
|             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) |  | ||||||
|             ->get(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Toby\Domain\Events\VacationRequestCreated; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestCreatedNotification; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestCreatedOnEmployeeBehalf; |  | ||||||
|  |  | ||||||
| class SendCreatedVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestCreated $event): void |  | ||||||
|     { |  | ||||||
|         $vacationRequest = $event->vacationRequest; |  | ||||||
|  |  | ||||||
|         if ($vacationRequest->creator->is($vacationRequest->user)) { |  | ||||||
|             $vacationRequest->user->notify(new VacationRequestCreatedNotification($vacationRequest)); |  | ||||||
|         } else { |  | ||||||
|             $vacationRequest->user->notify(new VacationRequestCreatedOnEmployeeBehalf($vacationRequest)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Toby\Domain\Enums\Role; |  | ||||||
| use Toby\Domain\Events\VacationRequestRejected; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestRejectedNotification; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class SendRejectedVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestRejected $event): void |  | ||||||
|     { |  | ||||||
|         foreach ($this->getUsersForNotifications() as $user) { |  | ||||||
|             $user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $user)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $event->vacationRequest->user->notify(new VacationRequestRejectedNotification($event->vacationRequest, $event->vacationRequest->user)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function getUsersForNotifications(): Collection |  | ||||||
|     { |  | ||||||
|         return User::query() |  | ||||||
|             ->whereIn("role", [Role::TechnicalApprover, Role::AdministrativeApprover]) |  | ||||||
|             ->get(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Toby\Domain\Enums\Role; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForAdminApproval; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestWaitsForAdminApprovalNotification; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class SendWaitedForAdministrativeVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestWaitsForAdminApproval $event): void |  | ||||||
|     { |  | ||||||
|         foreach ($this->getUsersForNotifications() as $user) { |  | ||||||
|             $user->notify(new VacationRequestWaitsForAdminApprovalNotification($event->vacationRequest, $user)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function getUsersForNotifications(): Collection |  | ||||||
|     { |  | ||||||
|         return User::query() |  | ||||||
|             ->where("role", [Role::AdministrativeApprover]) |  | ||||||
|             ->get(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Listeners; |  | ||||||
|  |  | ||||||
| use Illuminate\Support\Collection; |  | ||||||
| use Toby\Domain\Enums\Role; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForTechApproval; |  | ||||||
| use Toby\Domain\Notifications\VacationRequestWaitsForTechApprovalNotification; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class SendWaitedForTechnicalVacationRequestNotification |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function handle(VacationRequestWaitsForTechApproval $event): void |  | ||||||
|     { |  | ||||||
|         foreach ($this->getUsersForNotifications() as $user) { |  | ||||||
|             $user->notify(new VacationRequestWaitsForTechApprovalNotification($event->vacationRequest, $user)); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function getUsersForNotifications(): Collection |  | ||||||
|     { |  | ||||||
|         return User::query() |  | ||||||
|             ->where("role", [Role::TechnicalApprover]) |  | ||||||
|             ->get(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
							
								
								
									
										11
									
								
								app/Domain/Notifications/Channels.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/Domain/Notifications/Channels.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | class Channels | ||||||
|  | { | ||||||
|  |     public const MAIL = "mail"; | ||||||
|  |     public const SLACK = "slack"; | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenGivenNotification.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
|  | class KeyHasBeenGivenNotification extends Notification | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected User $sender, | ||||||
|  |         protected User $recipient, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(Notifiable $notifiable): SlackMessage | ||||||
|  |     { | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text(__(":sender gives key no :key to :recipient", [ | ||||||
|  |                 "sender" => $this->getName($this->sender), | ||||||
|  |                 "recipient" => $this->getName($this->recipient), | ||||||
|  |                 "key" => $notifiable->id, | ||||||
|  |             ])); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getName(User $user): string | ||||||
|  |     { | ||||||
|  |         if ($user->profile->slack_id !== null) { | ||||||
|  |             return "<@{$user->profile->slack_id}>"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->profile->full_name; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								app/Domain/Notifications/KeyHasBeenTakenNotification.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
|  | class KeyHasBeenTakenNotification extends Notification | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected User $recipient, | ||||||
|  |         protected User $sender, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(Notifiable $notifiable): SlackMessage | ||||||
|  |     { | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text(__(":recipient takes key no :key from :sender", [ | ||||||
|  |                 "recipient" => $this->getName($this->recipient), | ||||||
|  |                 "sender" => $this->getName($this->sender), | ||||||
|  |                 "key" => $notifiable->id, | ||||||
|  |             ])); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getName(User $user): string | ||||||
|  |     { | ||||||
|  |         if ($user->profile->slack_id !== null) { | ||||||
|  |             return "<@{$user->profile->slack_id}>"; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->profile->full_name; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										10
									
								
								app/Domain/Notifications/Notifiable.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								app/Domain/Notifications/Notifiable.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | interface Notifiable | ||||||
|  | { | ||||||
|  |     public function notify($instance); | ||||||
|  | } | ||||||
| @@ -9,6 +9,7 @@ use Illuminate\Notifications\Messages\MailMessage; | |||||||
| use Illuminate\Notifications\Notification; | use Illuminate\Notifications\Notification; | ||||||
| use InvalidArgumentException; | use InvalidArgumentException; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  |  | ||||||
| class VacationRequestCreatedNotification extends Notification | class VacationRequestCreatedNotification extends Notification | ||||||
| { | { | ||||||
| @@ -20,7 +21,16 @@ class VacationRequestCreatedNotification extends Notification | |||||||
|  |  | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |         $seeDetails = __("See details"); | ||||||
|  |  | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|${seeDetails}>"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
| @@ -39,33 +49,64 @@ class VacationRequestCreatedNotification extends Notification | |||||||
|  |  | ||||||
|     protected function buildMailMessage(string $url): MailMessage |     protected function buildMailMessage(string $url): MailMessage | ||||||
|     { |     { | ||||||
|         $user = $this->vacationRequest->user->first_name; |         $user = $this->vacationRequest->user->profile->first_name; | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |         $type = $this->vacationRequest->type->label(); | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |         $from = $this->vacationRequest->from->toDisplayString(); | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |         $to = $this->vacationRequest->to->toDisplayString(); | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |         $days = $this->vacationRequest->vacations()->count(); | ||||||
|         $appName = config("app.name"); |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |         return (new MailMessage()) | ||||||
|             ->greeting(__("Hi :user!", [ |             ->greeting( | ||||||
|                 "user" => $user, |                 __("Hi :user!", [ | ||||||
|             ])) |                     "user" => $user, | ||||||
|             ->subject(__("Vacation request :title has been created", [ |                 ]), | ||||||
|                 "title" => $title, |             ) | ||||||
|             ])) |             ->subject($this->buildSubject()) | ||||||
|             ->line(__("The vacation request :title has been created correctly in the :appName.", [ |             ->line($this->buildDescription()) | ||||||
|                 "title" => $title, |             ->line( | ||||||
|                 "appName" => $appName, |                 __("Vacation type: :type", [ | ||||||
|             ])) |                     "type" => $type, | ||||||
|             ->line(__("Vacation type: :type", [ |                 ]), | ||||||
|                 "type" => $type, |             ) | ||||||
|             ])) |             ->line( | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |                 __("From :from to :to (number of days: :days)", [ | ||||||
|                 "from" => $from, |                     "from" => $from, | ||||||
|                 "to" => $to, |                     "to" => $to, | ||||||
|                 "days" => $days, |                     "days" => $days, | ||||||
|             ])) |                 ]), | ||||||
|  |             ) | ||||||
|             ->action(__("Click here for details"), $url); |             ->action(__("Click here for details"), $url); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     protected function buildSubject(): string | ||||||
|  |     { | ||||||
|  |         $name = $this->vacationRequest->name; | ||||||
|  |  | ||||||
|  |         if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) { | ||||||
|  |             return __("Vacation request :title has been created", [ | ||||||
|  |                 "title" => $name, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return __("Vacation request :title has been created on your behalf", [ | ||||||
|  |             "title" => $name, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function buildDescription(): string | ||||||
|  |     { | ||||||
|  |         $name = $this->vacationRequest->name; | ||||||
|  |  | ||||||
|  |         if ($this->vacationRequest->creator()->is($this->vacationRequest->user)) { | ||||||
|  |             return __("The vacation request :title has been created successfully.", [ | ||||||
|  |                 "requester" => $this->vacationRequest->user->profile->full_name, | ||||||
|  |                 "title" => $name, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return __("The vacation request :title has been created successfully by user :creator on your behalf.", [ | ||||||
|  |             "title" => $this->vacationRequest->name, | ||||||
|  |             "creator" => $this->vacationRequest->creator->profile->full_name, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,73 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Notifications; |  | ||||||
|  |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Notifications\Notification; |  | ||||||
| use InvalidArgumentException; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestCreatedOnEmployeeBehalf extends Notification |  | ||||||
| { |  | ||||||
|     use Queueable; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         protected VacationRequest $vacationRequest, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function via(): array |  | ||||||
|     { |  | ||||||
|         return ["mail"]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @throws InvalidArgumentException |  | ||||||
|      */ |  | ||||||
|     public function toMail(): MailMessage |  | ||||||
|     { |  | ||||||
|         $url = route( |  | ||||||
|             "vacation.requests.show", |  | ||||||
|             [ |  | ||||||
|                 "vacationRequest" => $this->vacationRequest, |  | ||||||
|             ], |  | ||||||
|         ); |  | ||||||
|         return $this->buildMailMessage($url); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function buildMailMessage(string $url): MailMessage |  | ||||||
|     { |  | ||||||
|         $creator = $this->vacationRequest->creator->fullName; |  | ||||||
|         $user = $this->vacationRequest->user->first_name; |  | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |  | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |  | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |  | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |  | ||||||
|         $appName = config("app.name"); |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |  | ||||||
|             ->greeting(__("Hi :user!", [ |  | ||||||
|                 "user" => $user, |  | ||||||
|             ])) |  | ||||||
|             ->subject(__("Vacation request :title has been created on your behalf", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title has been created correctly by user :creator on your behalf in the :appName.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "appName" => $appName, |  | ||||||
|                 "creator" => $creator, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |  | ||||||
|                 "type" => $type, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |  | ||||||
|                 "from" => $from, |  | ||||||
|                 "to" => $to, |  | ||||||
|                 "days" => $days, |  | ||||||
|             ])) |  | ||||||
|             ->action(__("Click here for details"), $url); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,74 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Notifications; |  | ||||||
|  |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Notifications\Notification; |  | ||||||
| use InvalidArgumentException; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestRejectedNotification extends Notification |  | ||||||
| { |  | ||||||
|     use Queueable; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         protected VacationRequest $vacationRequest, |  | ||||||
|         protected User $user, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function via(): array |  | ||||||
|     { |  | ||||||
|         return ["mail"]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @throws InvalidArgumentException |  | ||||||
|      */ |  | ||||||
|     public function toMail(): MailMessage |  | ||||||
|     { |  | ||||||
|         $url = route( |  | ||||||
|             "vacation.requests.show", |  | ||||||
|             [ |  | ||||||
|                 "vacationRequest" => $this->vacationRequest, |  | ||||||
|             ], |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         return $this->buildMailMessage($url); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function buildMailMessage(string $url): MailMessage |  | ||||||
|     { |  | ||||||
|         $user = $this->user->first_name; |  | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |  | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |  | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |  | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |  | ||||||
|         $requester = $this->vacationRequest->user->fullName; |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |  | ||||||
|             ->greeting(__("Hi :user!", [ |  | ||||||
|                 "user" => $user, |  | ||||||
|             ])) |  | ||||||
|             ->subject(__("Vacation request :title has been rejected", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title for user :requester has been rejected.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |  | ||||||
|                 "type" => $type, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |  | ||||||
|                 "from" => $from, |  | ||||||
|                 "to" => $to, |  | ||||||
|                 "days" => $days, |  | ||||||
|             ])) |  | ||||||
|             ->action(__("Click here for details"), $url); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -10,8 +10,9 @@ use Illuminate\Notifications\Notification; | |||||||
| use InvalidArgumentException; | use InvalidArgumentException; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
| 
 | 
 | ||||||
| class VacationRequestApprovedNotification extends Notification | class VacationRequestStatusChangedNotification extends Notification | ||||||
| { | { | ||||||
|     use Queueable; |     use Queueable; | ||||||
| 
 | 
 | ||||||
| @@ -22,7 +23,16 @@ class VacationRequestApprovedNotification extends Notification | |||||||
| 
 | 
 | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |         $seeDetails = __("See details"); | ||||||
|  | 
 | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|${seeDetails}>"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -42,25 +52,18 @@ class VacationRequestApprovedNotification extends Notification | |||||||
| 
 | 
 | ||||||
|     protected function buildMailMessage(string $url): MailMessage |     protected function buildMailMessage(string $url): MailMessage | ||||||
|     { |     { | ||||||
|         $user = $this->user->first_name; |         $user = $this->user->profile->first_name; | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |         $type = $this->vacationRequest->type->label(); | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |         $from = $this->vacationRequest->from->toDisplayString(); | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |         $to = $this->vacationRequest->to->toDisplayString(); | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |         $days = $this->vacationRequest->vacations()->count(); | ||||||
|         $requester = $this->vacationRequest->user->fullName; |  | ||||||
| 
 | 
 | ||||||
|         return (new MailMessage()) |         return (new MailMessage()) | ||||||
|             ->greeting(__("Hi :user!", [ |             ->greeting(__("Hi :user!", [ | ||||||
|                 "user" => $user, |                 "user" => $user, | ||||||
|             ])) |             ])) | ||||||
|             ->subject(__("Vacation request :title has been approved", [ |             ->subject($this->buildSubject()) | ||||||
|                 "title" => $title, |             ->line($this->buildDescription()) | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title for user :requester has been approved.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |             ->line(__("Vacation type: :type", [ | ||||||
|                 "type" => $type, |                 "type" => $type, | ||||||
|             ])) |             ])) | ||||||
| @@ -71,4 +74,21 @@ class VacationRequestApprovedNotification extends Notification | |||||||
|             ])) |             ])) | ||||||
|             ->action(__("Click here for details"), $url); |             ->action(__("Click here for details"), $url); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     protected function buildSubject(): string | ||||||
|  |     { | ||||||
|  |         return __("Vacation request :title has been :status", [ | ||||||
|  |             "title" => $this->vacationRequest->name, | ||||||
|  |             "status" => $this->vacationRequest->state->label(), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function buildDescription(): string | ||||||
|  |     { | ||||||
|  |         return __("The vacation request :title from user :requester has been :status.", [ | ||||||
|  |             "title" => $this->vacationRequest->name, | ||||||
|  |             "requester" => $this->vacationRequest->user->profile->full_name, | ||||||
|  |             "status" => $this->vacationRequest->state->label(), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,74 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Notifications; |  | ||||||
|  |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Notifications\Notification; |  | ||||||
| use InvalidArgumentException; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestWaitsForAdminApprovalNotification extends Notification |  | ||||||
| { |  | ||||||
|     use Queueable; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         protected VacationRequest $vacationRequest, |  | ||||||
|         protected User $user, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function via(): array |  | ||||||
|     { |  | ||||||
|         return ["mail"]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @throws InvalidArgumentException |  | ||||||
|      */ |  | ||||||
|     public function toMail(): MailMessage |  | ||||||
|     { |  | ||||||
|         $url = route( |  | ||||||
|             "vacation.requests.show", |  | ||||||
|             [ |  | ||||||
|                 "vacationRequest" => $this->vacationRequest, |  | ||||||
|             ], |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         return $this->buildMailMessage($url); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function buildMailMessage(string $url): MailMessage |  | ||||||
|     { |  | ||||||
|         $user = $this->user->first_name; |  | ||||||
|         $requester = $this->vacationRequest->user->fullName; |  | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |  | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |  | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |  | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |  | ||||||
|             ->greeting(__("Hi :user!", [ |  | ||||||
|                 "user" => $user, |  | ||||||
|             ])) |  | ||||||
|             ->subject(__("Vacation request :title is waiting for your approval", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title from user: :requester is waiting for your approval.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |  | ||||||
|                 "type" => $type, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |  | ||||||
|                 "from" => $from, |  | ||||||
|                 "to" => $to, |  | ||||||
|                 "days" => $days, |  | ||||||
|             ])) |  | ||||||
|             ->action(__("Click here for details"), $url); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -8,10 +8,12 @@ use Illuminate\Bus\Queueable; | |||||||
| use Illuminate\Notifications\Messages\MailMessage; | use Illuminate\Notifications\Messages\MailMessage; | ||||||
| use Illuminate\Notifications\Notification; | use Illuminate\Notifications\Notification; | ||||||
| use InvalidArgumentException; | use InvalidArgumentException; | ||||||
|  | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
| 
 | 
 | ||||||
| class VacationRequestCancelledNotification extends Notification | class VacationRequestWaitsForApprovalNotification extends Notification | ||||||
| { | { | ||||||
|     use Queueable; |     use Queueable; | ||||||
| 
 | 
 | ||||||
| @@ -22,7 +24,16 @@ class VacationRequestCancelledNotification extends Notification | |||||||
| 
 | 
 | ||||||
|     public function via(): array |     public function via(): array | ||||||
|     { |     { | ||||||
|         return ["mail"]; |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); | ||||||
|  |         $seeDetails = __("See details"); | ||||||
|  | 
 | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text("{$this->buildDescription()}\n <${url}|${seeDetails}>"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @@ -42,25 +53,18 @@ class VacationRequestCancelledNotification extends Notification | |||||||
| 
 | 
 | ||||||
|     protected function buildMailMessage(string $url): MailMessage |     protected function buildMailMessage(string $url): MailMessage | ||||||
|     { |     { | ||||||
|         $user = $this->user->first_name; |         $user = $this->user->profile->first_name; | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |         $type = $this->vacationRequest->type->label(); | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |         $from = $this->vacationRequest->from->toDisplayString(); | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |         $to = $this->vacationRequest->to->toDisplayString(); | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |         $days = $this->vacationRequest->vacations()->count(); | ||||||
|         $requester = $this->vacationRequest->user->fullName; |  | ||||||
| 
 | 
 | ||||||
|         return (new MailMessage()) |         return (new MailMessage()) | ||||||
|             ->greeting(__("Hi :user!", [ |             ->greeting(__("Hi :user!", [ | ||||||
|                 "user" => $user, |                 "user" => $user, | ||||||
|             ])) |             ])) | ||||||
|             ->subject(__("Vacation request :title has been cancelled", [ |             ->subject($this->buildSubject()) | ||||||
|                 "title" => $title, |             ->line($this->buildDescription()) | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title for user :requester has been cancelled.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |             ->line(__("Vacation type: :type", [ | ||||||
|                 "type" => $type, |                 "type" => $type, | ||||||
|             ])) |             ])) | ||||||
| @@ -71,4 +75,37 @@ class VacationRequestCancelledNotification extends Notification | |||||||
|             ])) |             ])) | ||||||
|             ->action(__("Click here for details"), $url); |             ->action(__("Click here for details"), $url); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     protected function buildSubject(): string | ||||||
|  |     { | ||||||
|  |         $title = $this->vacationRequest->name; | ||||||
|  | 
 | ||||||
|  |         if ($this->vacationRequest->state->equals(WaitingForTechnical::class)) { | ||||||
|  |             return __("Vacation request :title is waiting for your technical approval", [ | ||||||
|  |                 "title" => $title, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return __("Vacation request :title is waiting for your administrative approval", [ | ||||||
|  |             "title" => $title, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function buildDescription(): string | ||||||
|  |     { | ||||||
|  |         $title = $this->vacationRequest->name; | ||||||
|  |         $requester = $this->vacationRequest->user->profile->full_name; | ||||||
|  | 
 | ||||||
|  |         if ($this->vacationRequest->state->equals(WaitingForTechnical::class)) { | ||||||
|  |             return __("The vacation request :title from user :requester is waiting for your technical approval.", [ | ||||||
|  |                 "title" => $title, | ||||||
|  |                 "requester" => $requester, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return __("The vacation request :title from user :requester is waiting for your administrative approval.", [ | ||||||
|  |             "title" => $title, | ||||||
|  |             "requester" => $requester, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -1,74 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Domain\Notifications; |  | ||||||
|  |  | ||||||
| use Illuminate\Bus\Queueable; |  | ||||||
| use Illuminate\Notifications\Messages\MailMessage; |  | ||||||
| use Illuminate\Notifications\Notification; |  | ||||||
| use InvalidArgumentException; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; |  | ||||||
|  |  | ||||||
| class VacationRequestWaitsForTechApprovalNotification extends Notification |  | ||||||
| { |  | ||||||
|     use Queueable; |  | ||||||
|  |  | ||||||
|     public function __construct( |  | ||||||
|         protected VacationRequest $vacationRequest, |  | ||||||
|         protected User $user, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function via(): array |  | ||||||
|     { |  | ||||||
|         return ["mail"]; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * @throws InvalidArgumentException |  | ||||||
|      */ |  | ||||||
|     public function toMail(): MailMessage |  | ||||||
|     { |  | ||||||
|         $url = route( |  | ||||||
|             "vacation.requests.show", |  | ||||||
|             [ |  | ||||||
|                 "vacationRequest" => $this->vacationRequest, |  | ||||||
|             ], |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         return $this->buildMailMessage($url); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function buildMailMessage(string $url): MailMessage |  | ||||||
|     { |  | ||||||
|         $user = $this->user->first_name; |  | ||||||
|         $requester = $this->vacationRequest->user->fullName; |  | ||||||
|         $title = $this->vacationRequest->name; |  | ||||||
|         $type = $this->vacationRequest->type->label(); |  | ||||||
|         $from = $this->vacationRequest->from->toDisplayString(); |  | ||||||
|         $to = $this->vacationRequest->to->toDisplayString(); |  | ||||||
|         $days = $this->vacationRequest->vacations()->count(); |  | ||||||
|  |  | ||||||
|         return (new MailMessage()) |  | ||||||
|             ->greeting(__("Hi :user!", [ |  | ||||||
|                 "user" => $user, |  | ||||||
|             ])) |  | ||||||
|             ->subject(__("Vacation request :title is waiting for your approval", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("The vacation request :title from user: :requester is waiting for your approval.", [ |  | ||||||
|                 "title" => $title, |  | ||||||
|                 "requester" => $requester, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("Vacation type: :type", [ |  | ||||||
|                 "type" => $type, |  | ||||||
|             ])) |  | ||||||
|             ->line(__("From :from to :to (number of days: :days)", [ |  | ||||||
|                 "from" => $from, |  | ||||||
|                 "to" => $to, |  | ||||||
|                 "days" => $days, |  | ||||||
|             ])) |  | ||||||
|             ->action(__("Click here for details"), $url); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Notifications; | ||||||
|  |  | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Notifications\Messages\MailMessage; | ||||||
|  | use Illuminate\Notifications\Notification; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\SlackMessage; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\VacationRequestsAttachment; | ||||||
|  |  | ||||||
|  | class VacationRequestsSummaryNotification extends Notification | ||||||
|  | { | ||||||
|  |     use Queueable; | ||||||
|  |  | ||||||
|  |     public function __construct( | ||||||
|  |         protected Carbon $day, | ||||||
|  |         protected Collection $vacationRequests, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function via(): array | ||||||
|  |     { | ||||||
|  |         return [Channels::MAIL, Channels::SLACK]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toSlack(): SlackMessage | ||||||
|  |     { | ||||||
|  |         return (new SlackMessage()) | ||||||
|  |             ->text(__("Requests wait for your approval - status for day :date:", ["date" => $this->day->toDisplayString()])) | ||||||
|  |             ->withAttachment(new VacationRequestsAttachment($this->vacationRequests)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function toMail(Notifiable $notifiable): MailMessage | ||||||
|  |     { | ||||||
|  |         $url = route( | ||||||
|  |             "vacation.requests.indexForApprovers", | ||||||
|  |             [ | ||||||
|  |                 "status" => "waiting_for_action", | ||||||
|  |             ], | ||||||
|  |         ); | ||||||
|  |  | ||||||
|  |         return $this->buildMailMessage($notifiable, $url); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function buildMailMessage(Notifiable $notifiable, string $url): MailMessage | ||||||
|  |     { | ||||||
|  |         $user = $notifiable->profile->first_name; | ||||||
|  |  | ||||||
|  |         $message = (new MailMessage()) | ||||||
|  |             ->greeting( | ||||||
|  |                 __("Hi :user!", [ | ||||||
|  |                     "user" => $user, | ||||||
|  |                 ]), | ||||||
|  |             ) | ||||||
|  |             ->line  (__("Requests list waits for your approval - status for day :date:", ["date" => $this->day->toDisplayString()])) | ||||||
|  |             ->subject(__("Requests wait for your approval - status for day :date:", ["date" => $this->day->toDisplayString()])); | ||||||
|  |  | ||||||
|  |         foreach ($this->vacationRequests as $request) { | ||||||
|  |             $url = route("vacation.requests.show", ["vacationRequest" => $request->id]); | ||||||
|  |  | ||||||
|  |             $message->line( | ||||||
|  |                 __("- [request no. :request](:url) of user :user (:startDate - :endDate)", ["request" => $request->name, "url" => $url, "user" => $request->user->profile->full_name, "startDate" => $request->from->toDisplayString(), "endDate" => $request->to->toDisplayString()]), | ||||||
|  |             ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $message | ||||||
|  |             ->action(__("Go to requests"), $url); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										26
									
								
								app/Domain/Policies/KeyPolicy.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								app/Domain/Policies/KeyPolicy.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Policies; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Eloquent\Models\Key; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class KeyPolicy | ||||||
|  | { | ||||||
|  |     public function manage(User $user): bool | ||||||
|  |     { | ||||||
|  |         return $user->role === Role::AdministrativeApprover; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function give(User $user, Key $key): bool | ||||||
|  |     { | ||||||
|  |         if ($key->user()->is($user)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $user->role === Role::AdministrativeApprover; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -5,6 +5,9 @@ declare(strict_types=1); | |||||||
| namespace Toby\Domain\Policies; | namespace Toby\Domain\Policies; | ||||||
|  |  | ||||||
| use Toby\Domain\Enums\Role; | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\States\VacationRequest\Created; | ||||||
|  | use Toby\Domain\States\VacationRequest\WaitingForAdministrative; | ||||||
|  | use Toby\Domain\States\VacationRequest\WaitingForTechnical; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| @@ -40,8 +43,16 @@ class VacationRequestPolicy | |||||||
|         return in_array($user->role, [Role::AdministrativeApprover, Role::TechnicalApprover], true); |         return in_array($user->role, [Role::AdministrativeApprover, Role::TechnicalApprover], true); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function cancel(User $user): bool |     public function cancel(User $user, VacationRequest $vacationRequest): bool | ||||||
|     { |     { | ||||||
|  |         if ($vacationRequest->user->is($user) && $vacationRequest->state->equals( | ||||||
|  |             Created::class, | ||||||
|  |             WaitingForAdministrative::class, | ||||||
|  |             WaitingForTechnical::class, | ||||||
|  |         )) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return $user->role === Role::AdministrativeApprover; |         return $user->role === Role::AdministrativeApprover; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -26,7 +26,7 @@ class PolishHolidaysRetriever | |||||||
|  |  | ||||||
|     protected function prepareHolidays(array $holidays): Collection |     protected function prepareHolidays(array $holidays): Collection | ||||||
|     { |     { | ||||||
|         return collect($holidays)->map(fn(Holiday $holiday) => [ |         return collect($holidays)->map(fn(Holiday $holiday): array => [ | ||||||
|             "name" => $holiday->getName([static::LANG_KEY]), |             "name" => $holiday->getName([static::LANG_KEY]), | ||||||
|             "date" => Carbon::createFromTimestamp($holiday->getTimestamp()), |             "date" => Carbon::createFromTimestamp($holiday->getTimestamp()), | ||||||
|         ])->values(); |         ])->values(); | ||||||
|   | |||||||
							
								
								
									
										113
									
								
								app/Domain/ResumeGenerator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								app/Domain/ResumeGenerator.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,113 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain; | ||||||
|  |  | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Illuminate\Support\Str; | ||||||
|  | use PhpOffice\PhpWord\TemplateProcessor; | ||||||
|  | use Toby\Eloquent\Models\Resume; | ||||||
|  |  | ||||||
|  | class ResumeGenerator | ||||||
|  | { | ||||||
|  |     public function generate(Resume $resume): string | ||||||
|  |     { | ||||||
|  |         $processor = new TemplateProcessor($this->getTemplate()); | ||||||
|  |  | ||||||
|  |         $processor->setValue("id", $resume->id); | ||||||
|  |         $processor->setValue("name", $resume->user ? $resume->user->profile->full_name : $resume->name); | ||||||
|  |  | ||||||
|  |         $this->fillTechnologies($processor, $resume); | ||||||
|  |         $this->fillLanguages($processor, $resume); | ||||||
|  |         $this->fillEducation($processor, $resume); | ||||||
|  |         $this->fillProjects($processor, $resume); | ||||||
|  |  | ||||||
|  |         return $processor->save(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getTemplate(): string | ||||||
|  |     { | ||||||
|  |         return resource_path("views/docx/resume_eng.docx"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function fillTechnologies(TemplateProcessor $processor, Resume $resume): void | ||||||
|  |     { | ||||||
|  |         $processor->cloneBlock("technologies", 0, true, false, $this->getTechnologies($resume)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function fillLanguages(TemplateProcessor $processor, Resume $resume): void | ||||||
|  |     { | ||||||
|  |         $processor->cloneBlock("languages", 0, true, false, $this->getLanguages($resume)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function fillEducation(TemplateProcessor $processor, Resume $resume): void | ||||||
|  |     { | ||||||
|  |         $processor->cloneBlock("education", 0, true, false, $this->getEducation($resume)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function fillProjects(TemplateProcessor $processor, Resume $resume): void | ||||||
|  |     { | ||||||
|  |         $processor->cloneBlock("projects", $resume->projects->count(), true, true); | ||||||
|  |  | ||||||
|  |         foreach ($resume->projects as $index => $project) { | ||||||
|  |             ++$index; | ||||||
|  |             $processor->setValues($this->getProject($project, $index)); | ||||||
|  |  | ||||||
|  |             $processor->cloneBlock("project_technologies#{$index}", 0, true, false, $this->getProjectTechnologies($project, $index)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getProject(array $project, int $index): array | ||||||
|  |     { | ||||||
|  |         return [ | ||||||
|  |             "index#{$index}" => $index, | ||||||
|  |             "start_date#{$index}" => Carbon::createFromFormat("m/Y", $project["startDate"])->format("n.Y"), | ||||||
|  |             "end_date#{$index}" => $project["current"] ? "present" : Carbon::createFromFormat("m/Y", $project["endDate"])->format("n.Y"), | ||||||
|  |             "description#{$index}" => $project["description"], | ||||||
|  |             "tasks#{$index}" => $this->withNewLines($project["tasks"]), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function withNewLines(string $text): string | ||||||
|  |     { | ||||||
|  |         return Str::replace("\n", "</w:t><w:br/><w:t>", $text); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getProjectTechnologies(array $project, int $index): array | ||||||
|  |     { | ||||||
|  |         $technologies = new Collection($project["technologies"] ?? []); | ||||||
|  |  | ||||||
|  |         return $technologies->map(fn(string $name) => [ | ||||||
|  |             "technology#{$index}" => $name, | ||||||
|  |         ])->all(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getTechnologies(Resume $resume): array | ||||||
|  |     { | ||||||
|  |         return $resume->technologies->map(fn(array $technology): array => [ | ||||||
|  |             "technology_name" => $technology["name"], | ||||||
|  |             "technology_level" => __("resume.technology_levels.{$technology["level"]}"), | ||||||
|  |         ])->all(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getLanguages(Resume $resume): array | ||||||
|  |     { | ||||||
|  |         return $resume->languages->map(fn(array $language): array => [ | ||||||
|  |             "language_name" => $language["name"], | ||||||
|  |             "language_level" => __("resume.language_levels.{$language["level"]}"), | ||||||
|  |         ])->all(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getEducation(Resume $resume): array | ||||||
|  |     { | ||||||
|  |         return $resume->education->map(fn(array $project, int $index): array => [ | ||||||
|  |             "start_date" => Carbon::createFromFormat("m/Y", $project["startDate"])->format("n.Y"), | ||||||
|  |             "end_date" => $project["current"] ? "present" : Carbon::createFromFormat("m/Y", $project["endDate"])->format("n.Y"), | ||||||
|  |             "school" => $project["school"], | ||||||
|  |             "field_of_study" => $project["fieldOfStudy"], | ||||||
|  |             "degree" => $project["degree"], | ||||||
|  |         ])->all(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -36,4 +36,9 @@ abstract class VacationRequestState extends State | |||||||
|                 Approved::class, |                 Approved::class, | ||||||
|             ], Cancelled::class); |             ], Cancelled::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function label(): string | ||||||
|  |     { | ||||||
|  |         return __(static::$name); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,20 +4,21 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain; | namespace Toby\Domain; | ||||||
|  |  | ||||||
| use Illuminate\Database\Eloquent\Collection; |  | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Maatwebsite\Excel\Concerns\WithMultipleSheets; | use Maatwebsite\Excel\Concerns\WithMultipleSheets; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
| class TimesheetExport implements WithMultipleSheets | class TimesheetExport implements WithMultipleSheets | ||||||
| { | { | ||||||
|     protected Collection $users; |     protected Collection $users; | ||||||
|  |     protected Collection $types; | ||||||
|     protected Carbon $month; |     protected Carbon $month; | ||||||
|  |  | ||||||
|     public function sheets(): array |     public function sheets(): array | ||||||
|     { |     { | ||||||
|         return $this->users |         return $this->users | ||||||
|             ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month)) |             ->map(fn(User $user): TimesheetPerUserSheet => new TimesheetPerUserSheet($user, $this->month, $this->types)) | ||||||
|             ->toArray(); |             ->toArray(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -34,4 +35,11 @@ class TimesheetExport implements WithMultipleSheets | |||||||
|  |  | ||||||
|         return $this; |         return $this; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function forVacationTypes(Collection $types): static | ||||||
|  |     { | ||||||
|  |         $this->types = $types; | ||||||
|  |  | ||||||
|  |         return $this; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ namespace Toby\Domain; | |||||||
| use Carbon\CarbonInterface; | use Carbon\CarbonInterface; | ||||||
| use Carbon\CarbonPeriod; | use Carbon\CarbonPeriod; | ||||||
| use Generator; | use Generator; | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Maatwebsite\Excel\Concerns\FromGenerator; | use Maatwebsite\Excel\Concerns\FromGenerator; | ||||||
| @@ -25,7 +26,6 @@ use PhpOffice\PhpSpreadsheet\Style\Fill; | |||||||
| use PhpOffice\PhpSpreadsheet\Style\NumberFormat; | use PhpOffice\PhpSpreadsheet\Style\NumberFormat; | ||||||
| use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; | use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
| use Toby\Domain\States\VacationRequest\Approved; |  | ||||||
| use Toby\Eloquent\Models\Holiday; | use Toby\Eloquent\Models\Holiday; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\Vacation; | use Toby\Eloquent\Models\Vacation; | ||||||
| @@ -41,17 +41,16 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With | |||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected User $user, |         protected User $user, | ||||||
|         protected Carbon $month, |         protected Carbon $month, | ||||||
|  |         protected Collection $types, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function title(): string |     public function title(): string | ||||||
|     { |     { | ||||||
|         return $this->user->fullName; |         return $this->user->profile->full_name; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function headings(): array |     public function headings(): array | ||||||
|     { |     { | ||||||
|         $types = VacationType::cases(); |  | ||||||
|  |  | ||||||
|         $headings = [ |         $headings = [ | ||||||
|             __("Date"), |             __("Date"), | ||||||
|             __("Day of week"), |             __("Day of week"), | ||||||
| @@ -60,7 +59,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With | |||||||
|             __("Worked hours"), |             __("Worked hours"), | ||||||
|         ]; |         ]; | ||||||
|  |  | ||||||
|         foreach ($types as $type) { |         foreach ($this->types as $type) { | ||||||
|             $headings[] = $type->label(); |             $headings[] = $type->label(); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -188,13 +187,14 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With | |||||||
|     { |     { | ||||||
|         return $user->vacations() |         return $user->vacations() | ||||||
|             ->with("vacationRequest") |             ->with("vacationRequest") | ||||||
|  |             ->whereRelation("vacationRequest", fn(Builder $query): Builder => $query->whereIn("type", $this->types)) | ||||||
|             ->whereBetween("date", [$period->start, $period->end]) |             ->whereBetween("date", [$period->start, $period->end]) | ||||||
|             ->whereRelation("vacationRequest", "state", Approved::$name) |             ->approved() | ||||||
|             ->get() |             ->get() | ||||||
|             ->groupBy( |             ->groupBy( | ||||||
|                 [ |                 [ | ||||||
|                     fn(Vacation $vacation) => $vacation->date->toDateString(), |                     fn(Vacation $vacation): string => $vacation->date->toDateString(), | ||||||
|                     fn(Vacation $vacation) => $vacation->vacationRequest->type->value, |                     fn(Vacation $vacation): string => $vacation->vacationRequest->type->value, | ||||||
|                 ], |                 ], | ||||||
|             ); |             ); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -5,9 +5,10 @@ declare(strict_types=1); | |||||||
| namespace Toby\Domain; | namespace Toby\Domain; | ||||||
|  |  | ||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  |  | ||||||
| class UserVacationStatsRetriever | class UserVacationStatsRetriever | ||||||
| @@ -20,24 +21,39 @@ class UserVacationStatsRetriever | |||||||
|     { |     { | ||||||
|         return $user |         return $user | ||||||
|             ->vacations() |             ->vacations() | ||||||
|             ->where("year_period_id", $yearPeriod->id) |             ->whereBelongsTo($yearPeriod) | ||||||
|             ->whereRelation( |             ->whereRelation( | ||||||
|                 "vacationRequest", |                 "vacationRequest", | ||||||
|                 fn(Builder $query) => $query |                 fn(Builder $query): Builder => $query | ||||||
|                     ->whereIn("type", $this->getLimitableVacationTypes()) |                     ->whereIn("type", $this->getLimitableVacationTypes()) | ||||||
|                     ->states(VacationRequestStatesRetriever::successStates()), |                     ->states(VacationRequestStatesRetriever::successStates()), | ||||||
|             ) |             ) | ||||||
|             ->count(); |             ->count(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function getUsedVacationDaysByMonth(User $user, YearPeriod $yearPeriod): Collection | ||||||
|  |     { | ||||||
|  |         return $user->vacations() | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->whereRelation( | ||||||
|  |                 "vacationRequest", | ||||||
|  |                 fn(Builder $query): Builder => $query | ||||||
|  |                     ->whereIn("type", $this->getLimitableVacationTypes()) | ||||||
|  |                     ->states(VacationRequestStatesRetriever::successStates()), | ||||||
|  |             ) | ||||||
|  |             ->get() | ||||||
|  |             ->groupBy(fn(Vacation $vacation): string => strtolower($vacation->date->englishMonth)) | ||||||
|  |             ->map(fn(Collection $items): int => $items->count()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int |     public function getPendingVacationDays(User $user, YearPeriod $yearPeriod): int | ||||||
|     { |     { | ||||||
|         return $user |         return $user | ||||||
|             ->vacations() |             ->vacations() | ||||||
|             ->where("year_period_id", $yearPeriod->id) |             ->whereBelongsTo($yearPeriod) | ||||||
|             ->whereRelation( |             ->whereRelation( | ||||||
|                 "vacationRequest", |                 "vacationRequest", | ||||||
|                 fn(Builder $query) => $query |                 fn(Builder $query): Builder => $query | ||||||
|                     ->whereIn("type", $this->getLimitableVacationTypes()) |                     ->whereIn("type", $this->getLimitableVacationTypes()) | ||||||
|                     ->states(VacationRequestStatesRetriever::pendingStates()), |                     ->states(VacationRequestStatesRetriever::pendingStates()), | ||||||
|             ) |             ) | ||||||
| @@ -48,16 +64,26 @@ class UserVacationStatsRetriever | |||||||
|     { |     { | ||||||
|         return $user |         return $user | ||||||
|             ->vacations() |             ->vacations() | ||||||
|             ->where("year_period_id", $yearPeriod->id) |             ->whereBelongsTo($yearPeriod) | ||||||
|             ->whereRelation( |             ->whereRelation( | ||||||
|                 "vacationRequest", |                 "vacationRequest", | ||||||
|                 fn(Builder $query) => $query |                 fn(Builder $query): Builder => $query | ||||||
|                     ->whereIn("type", $this->getNotLimitableVacationTypes()) |                     ->whereIn("type", $this->getNotLimitableVacationTypes()) | ||||||
|  |                     ->whereNot("type", VacationType::HomeOffice) | ||||||
|                     ->states(VacationRequestStatesRetriever::successStates()), |                     ->states(VacationRequestStatesRetriever::successStates()), | ||||||
|             ) |             ) | ||||||
|             ->count(); |             ->count(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function getHomeOfficeDays(User $user, YearPeriod $yearPeriod): int | ||||||
|  |     { | ||||||
|  |         return $user | ||||||
|  |             ->vacations() | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->whereRelation("vacationRequest", "type", VacationType::HomeOffice) | ||||||
|  |             ->count(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function getRemainingVacationDays(User $user, YearPeriod $yearPeriod): int |     public function getRemainingVacationDays(User $user, YearPeriod $yearPeriod): int | ||||||
|     { |     { | ||||||
|         $limit = $this->getVacationDaysLimit($user, $yearPeriod); |         $limit = $this->getVacationDaysLimit($user, $yearPeriod); | ||||||
| @@ -70,24 +96,24 @@ class UserVacationStatsRetriever | |||||||
|     public function getVacationDaysLimit(User $user, YearPeriod $yearPeriod): int |     public function getVacationDaysLimit(User $user, YearPeriod $yearPeriod): int | ||||||
|     { |     { | ||||||
|         $limit = $user->vacationLimits() |         $limit = $user->vacationLimits() | ||||||
|             ->where("year_period_id", $yearPeriod->id) |             ->whereBelongsTo($yearPeriod) | ||||||
|             ->first() |             ->first() | ||||||
|             ->days; |             ?->days; | ||||||
|  |  | ||||||
|         return $limit ?? 0; |         return $limit ?? 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function getLimitableVacationTypes(): Collection |     protected function getLimitableVacationTypes(): Collection | ||||||
|     { |     { | ||||||
|         $types = new Collection(VacationType::cases()); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function getNotLimitableVacationTypes(): Collection |     protected function getNotLimitableVacationTypes(): Collection | ||||||
|     { |     { | ||||||
|         $types = new Collection(VacationType::cases()); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => !$this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => !$this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,17 +4,7 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain; | namespace Toby\Domain; | ||||||
|  |  | ||||||
| use Illuminate\Contracts\Auth\Factory as Auth; |  | ||||||
| use Illuminate\Contracts\Events\Dispatcher; | use Illuminate\Contracts\Events\Dispatcher; | ||||||
| use Toby\Domain\Events\VacationRequestAcceptedByAdministrative; |  | ||||||
| use Toby\Domain\Events\VacationRequestAcceptedByTechnical; |  | ||||||
| use Toby\Domain\Events\VacationRequestApproved; |  | ||||||
| use Toby\Domain\Events\VacationRequestCancelled; |  | ||||||
| use Toby\Domain\Events\VacationRequestCreated; |  | ||||||
| use Toby\Domain\Events\VacationRequestRejected; |  | ||||||
| use Toby\Domain\Events\VacationRequestStateChanged; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForAdminApproval; |  | ||||||
| use Toby\Domain\Events\VacationRequestWaitsForTechApproval; |  | ||||||
| use Toby\Domain\States\VacationRequest\AcceptedByAdministrative; | use Toby\Domain\States\VacationRequest\AcceptedByAdministrative; | ||||||
| use Toby\Domain\States\VacationRequest\AcceptedByTechnical; | use Toby\Domain\States\VacationRequest\AcceptedByTechnical; | ||||||
| use Toby\Domain\States\VacationRequest\Approved; | use Toby\Domain\States\VacationRequest\Approved; | ||||||
| @@ -29,63 +19,47 @@ use Toby\Eloquent\Models\VacationRequest; | |||||||
| class VacationRequestStateManager | class VacationRequestStateManager | ||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected Auth $auth, |  | ||||||
|         protected Dispatcher $dispatcher, |         protected Dispatcher $dispatcher, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function markAsCreated(VacationRequest $vacationRequest, ?User $user = null): void |     public function markAsCreated(VacationRequest $vacationRequest): void | ||||||
|     { |     { | ||||||
|         $this->fireStateChangedEvent($vacationRequest, null, $vacationRequest->state, $user); |         $this->createActivity($vacationRequest, null, $vacationRequest->state, $vacationRequest->creator); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestCreated($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function approve(VacationRequest $vacationRequest, ?User $user = null): void |     public function approve(VacationRequest $vacationRequest, ?User $user = null): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, Approved::class, $user); |         $this->changeState($vacationRequest, Approved::class, $user); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestApproved($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function reject(VacationRequest $vacationRequest, ?User $user = null): void |     public function reject(VacationRequest $vacationRequest, User $user): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, Rejected::class, $user); |         $this->changeState($vacationRequest, Rejected::class, $user); | ||||||
|         $this->dispatcher->dispatch(new VacationRequestRejected($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function cancel(VacationRequest $vacationRequest, ?User $user = null): void |     public function cancel(VacationRequest $vacationRequest, User $user): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, Cancelled::class, $user); |         $this->changeState($vacationRequest, Cancelled::class, $user); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestCancelled($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function acceptAsTechnical(VacationRequest $vacationRequest, ?User $user = null): void |     public function acceptAsTechnical(VacationRequest $vacationRequest, User $user): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, AcceptedByTechnical::class, $user); |         $this->changeState($vacationRequest, AcceptedByTechnical::class, $user); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestAcceptedByTechnical($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function acceptAsAdministrative(VacationRequest $vacationRequest, ?User $user = null): void |     public function acceptAsAdministrative(VacationRequest $vacationRequest, User $user): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, AcceptedByAdministrative::class, $user); |         $this->changeState($vacationRequest, AcceptedByAdministrative::class, $user); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestAcceptedByAdministrative($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function waitForTechnical(VacationRequest $vacationRequest, ?User $user = null): void |     public function waitForTechnical(VacationRequest $vacationRequest): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, WaitingForTechnical::class, $user); |         $this->changeState($vacationRequest, WaitingForTechnical::class); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void |     public function waitForAdministrative(VacationRequest $vacationRequest): void | ||||||
|     { |     { | ||||||
|         $this->changeState($vacationRequest, WaitingForAdministrative::class, $user); |         $this->changeState($vacationRequest, WaitingForAdministrative::class); | ||||||
|  |  | ||||||
|         $this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void |     protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void | ||||||
| @@ -94,16 +68,19 @@ class VacationRequestStateManager | |||||||
|         $vacationRequest->state->transitionTo($state); |         $vacationRequest->state->transitionTo($state); | ||||||
|         $vacationRequest->save(); |         $vacationRequest->save(); | ||||||
|  |  | ||||||
|         $this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state, $user); |         $this->createActivity($vacationRequest, $previousState, $vacationRequest->state, $user); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function fireStateChangedEvent( |     protected function createActivity( | ||||||
|         VacationRequest $vacationRequest, |         VacationRequest $vacationRequest, | ||||||
|         ?VacationRequestState $from, |         ?VacationRequestState $from, | ||||||
|         VacationRequestState $to, |         VacationRequestState $to, | ||||||
|         ?User $user = null, |         ?User $user = null, | ||||||
|     ): void { |     ): void { | ||||||
|         $event = new VacationRequestStateChanged($vacationRequest, $from, $to, $user); |         $vacationRequest->activities()->create([ | ||||||
|         $this->dispatcher->dispatch($event); |             "from" => $from, | ||||||
|  |             "to" => $to, | ||||||
|  |             "user_id" => $user?->id, | ||||||
|  |         ]); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ declare(strict_types=1); | |||||||
| namespace Toby\Domain; | namespace Toby\Domain; | ||||||
|  |  | ||||||
| use Illuminate\Contracts\Config\Repository; | use Illuminate\Contracts\Config\Repository; | ||||||
|  | use Toby\Domain\Enums\EmploymentForm; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
|  |  | ||||||
| class VacationTypeConfigRetriever | class VacationTypeConfigRetriever | ||||||
| @@ -13,6 +14,8 @@ class VacationTypeConfigRetriever | |||||||
|     public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval"; |     public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval"; | ||||||
|     public const KEY_BILLABLE = "billable"; |     public const KEY_BILLABLE = "billable"; | ||||||
|     public const KEY_HAS_LIMIT = "has_limit"; |     public const KEY_HAS_LIMIT = "has_limit"; | ||||||
|  |     public const KEY_AVAILABLE_FOR = "available_for"; | ||||||
|  |     public const KEY_IS_VACATION = "is_vacation"; | ||||||
|  |  | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected Repository $config, |         protected Repository $config, | ||||||
| @@ -38,6 +41,16 @@ class VacationTypeConfigRetriever | |||||||
|         return $this->getConfigFor($type)[static::KEY_HAS_LIMIT]; |         return $this->getConfigFor($type)[static::KEY_HAS_LIMIT]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function isVacation(VacationType $type): bool | ||||||
|  |     { | ||||||
|  |         return $this->getConfigFor($type)[static::KEY_IS_VACATION]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     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 |     protected function getConfigFor(VacationType $type): array | ||||||
|     { |     { | ||||||
|         return $this->config->get("vacation_types.{$type->value}"); |         return $this->config->get("vacation_types.{$type->value}"); | ||||||
|   | |||||||
| @@ -5,11 +5,11 @@ declare(strict_types=1); | |||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Domain\Enums\VacationType; | use Toby\Domain\Enums\VacationType; | ||||||
| use Toby\Domain\VacationDaysCalculator; |  | ||||||
| use Toby\Domain\VacationRequestStatesRetriever; | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
| use Toby\Domain\VacationTypeConfigRetriever; | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| @@ -18,7 +18,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected VacationTypeConfigRetriever $configRetriever, |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|         protected VacationDaysCalculator $vacationDaysCalculator, |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function check(VacationRequest $vacationRequest): bool |     public function check(VacationRequest $vacationRequest): bool | ||||||
| @@ -29,7 +29,9 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
|  |  | ||||||
|         $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); |         $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); | ||||||
|         $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); |         $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); | ||||||
|         $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count(); |         $estimatedDays = $this->workDaysCalculator | ||||||
|  |             ->calculateDays($vacationRequest->from, $vacationRequest->to) | ||||||
|  |             ->count(); | ||||||
|  |  | ||||||
|         return $limit >= ($vacationDays + $estimatedDays); |         return $limit >= ($vacationDays + $estimatedDays); | ||||||
|     } |     } | ||||||
| @@ -41,16 +43,19 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
|  |  | ||||||
|     protected function getUserVacationLimit(User $user, YearPeriod $yearPeriod): int |     protected function getUserVacationLimit(User $user, YearPeriod $yearPeriod): int | ||||||
|     { |     { | ||||||
|         return $user->vacationLimits()->where("year_period_id", $yearPeriod->id)->first()->days ?? 0; |         return $user->vacationLimits() | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->first() | ||||||
|  |             ?->days ?? 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function getVacationDaysWithLimit(User $user, YearPeriod $yearPeriod): int |     protected function getVacationDaysWithLimit(User $user, YearPeriod $yearPeriod): int | ||||||
|     { |     { | ||||||
|         return $user->vacations() |         return $user->vacations() | ||||||
|             ->where("year_period_id", $yearPeriod->id) |             ->whereBelongsTo($yearPeriod) | ||||||
|             ->whereRelation( |             ->whereRelation( | ||||||
|                 "vacationRequest", |                 "vacationRequest", | ||||||
|                 fn(Builder $query) => $query |                 fn(Builder $query): Builder => $query | ||||||
|                     ->whereIn("type", $this->getLimitableVacationTypes()) |                     ->whereIn("type", $this->getLimitableVacationTypes()) | ||||||
|                     ->noStates(VacationRequestStatesRetriever::failedStates()), |                     ->noStates(VacationRequestStatesRetriever::failedStates()), | ||||||
|             ) |             ) | ||||||
| @@ -59,8 +64,8 @@ class DoesNotExceedLimitRule implements VacationRequestRule | |||||||
|  |  | ||||||
|     protected function getLimitableVacationTypes(): Collection |     protected function getLimitableVacationTypes(): Collection | ||||||
|     { |     { | ||||||
|         $types = new Collection(VacationType::cases()); |         $types = VacationType::all(); | ||||||
|  |  | ||||||
|         return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type)); |         return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,19 +4,19 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Domain\Validation\Rules; | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
| use Toby\Domain\VacationDaysCalculator; | use Toby\Domain\WorkDaysCalculator; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class MinimumOneVacationDayRule implements VacationRequestRule | class MinimumOneVacationDayRule implements VacationRequestRule | ||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected VacationDaysCalculator $vacationDaysCalculator, |         protected WorkDaysCalculator $workDaysCalculator, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function check(VacationRequest $vacationRequest): bool |     public function check(VacationRequest $vacationRequest): bool | ||||||
|     { |     { | ||||||
|         return $this->vacationDaysCalculator |         return $this->workDaysCalculator | ||||||
|             ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) |             ->calculateDays($vacationRequest->from, $vacationRequest->to) | ||||||
|             ->isNotEmpty(); |             ->isNotEmpty(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -9,5 +9,6 @@ use Toby\Eloquent\Models\VacationRequest; | |||||||
| interface VacationRequestRule | interface VacationRequestRule | ||||||
| { | { | ||||||
|     public function check(VacationRequest $vacationRequest): bool; |     public function check(VacationRequest $vacationRequest): bool; | ||||||
|  |  | ||||||
|     public function errorMessage(): string; |     public function errorMessage(): string; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										31
									
								
								app/Domain/Validation/Rules/VacationTypeCanBeSelected.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								app/Domain/Validation/Rules/VacationTypeCanBeSelected.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Domain\Validation\Rules; | ||||||
|  |  | ||||||
|  | use Toby\Domain\Enums\VacationType; | ||||||
|  | use Toby\Domain\VacationTypeConfigRetriever; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class VacationTypeCanBeSelected implements VacationRequestRule | ||||||
|  | { | ||||||
|  |     public function __construct( | ||||||
|  |         protected VacationTypeConfigRetriever $configRetriever, | ||||||
|  |     ) {} | ||||||
|  |  | ||||||
|  |     public function check(VacationRequest $vacationRequest): bool | ||||||
|  |     { | ||||||
|  |         $employmentForm = $vacationRequest->user->profile->employment_form; | ||||||
|  |  | ||||||
|  |         $availableTypes = VacationType::all() | ||||||
|  |             ->filter(fn(VacationType $type): bool => $this->configRetriever->isAvailableFor($type, $employmentForm)); | ||||||
|  |  | ||||||
|  |         return $availableTypes->contains($vacationRequest->type); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function errorMessage(): string | ||||||
|  |     { | ||||||
|  |         return __("You cannot create vacation request of this type."); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -12,6 +12,7 @@ use Toby\Domain\Validation\Rules\NoApprovedVacationRequestsInRange; | |||||||
| use Toby\Domain\Validation\Rules\NoPendingVacationRequestInRange; | use Toby\Domain\Validation\Rules\NoPendingVacationRequestInRange; | ||||||
| use Toby\Domain\Validation\Rules\VacationRangeIsInTheSameYearRule; | use Toby\Domain\Validation\Rules\VacationRangeIsInTheSameYearRule; | ||||||
| use Toby\Domain\Validation\Rules\VacationRequestRule; | use Toby\Domain\Validation\Rules\VacationRequestRule; | ||||||
|  | use Toby\Domain\Validation\Rules\VacationTypeCanBeSelected; | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class VacationRequestValidator | class VacationRequestValidator | ||||||
| @@ -19,6 +20,7 @@ class VacationRequestValidator | |||||||
|     protected array $rules = [ |     protected array $rules = [ | ||||||
|         VacationRangeIsInTheSameYearRule::class, |         VacationRangeIsInTheSameYearRule::class, | ||||||
|         MinimumOneVacationDayRule::class, |         MinimumOneVacationDayRule::class, | ||||||
|  |         VacationTypeCanBeSelected::class, | ||||||
|         DoesNotExceedLimitRule::class, |         DoesNotExceedLimitRule::class, | ||||||
|         NoPendingVacationRequestInRange::class, |         NoPendingVacationRequestInRange::class, | ||||||
|         NoApprovedVacationRequestsInRange::class, |         NoApprovedVacationRequestsInRange::class, | ||||||
|   | |||||||
| @@ -9,11 +9,12 @@ use Carbon\CarbonPeriod; | |||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Toby\Eloquent\Models\YearPeriod; | use Toby\Eloquent\Models\YearPeriod; | ||||||
| 
 | 
 | ||||||
| class VacationDaysCalculator | class WorkDaysCalculator | ||||||
| { | { | ||||||
|     public function calculateDays(YearPeriod $yearPeriod, CarbonInterface $from, CarbonInterface $to): Collection |     public function calculateDays(CarbonInterface $from, CarbonInterface $to): Collection | ||||||
|     { |     { | ||||||
|         $period = CarbonPeriod::create($from, $to); |         $period = CarbonPeriod::create($from, $to); | ||||||
|  |         $yearPeriod = YearPeriod::findByYear($from->year); | ||||||
|         $holidays = $yearPeriod->holidays()->pluck("date"); |         $holidays = $yearPeriod->holidays()->pluck("date"); | ||||||
| 
 | 
 | ||||||
|         $validDays = new Collection(); |         $validDays = new Collection(); | ||||||
							
								
								
									
										33
									
								
								app/Eloquent/Helpers/ColorGenerator.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								app/Eloquent/Helpers/ColorGenerator.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Helpers; | ||||||
|  |  | ||||||
|  | class ColorGenerator | ||||||
|  | { | ||||||
|  |     public static function generate(string $text): string | ||||||
|  |     { | ||||||
|  |         $colors = config("colors"); | ||||||
|  |         $hash = static::calculateHash($text); | ||||||
|  |  | ||||||
|  |         $index = $hash - count($colors) * floor($hash / count($colors)); | ||||||
|  |  | ||||||
|  |         return $colors[$index]; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected static function calculateHash(string $text): int | ||||||
|  |     { | ||||||
|  |         $hash = 0; | ||||||
|  |  | ||||||
|  |         if (empty($text)) { | ||||||
|  |             return $hash; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         for ($i = 0; $i < mb_strlen($text); $i++) { | ||||||
|  |             $hash = abs((int)(($hash << 2) - $hash) + mb_ord($text[$i])); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $hash; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -30,22 +30,20 @@ class YearPeriodRetriever | |||||||
|  |  | ||||||
|     public function links(): array |     public function links(): array | ||||||
|     { |     { | ||||||
|         $current = $this->selected(); |         $selected = $this->selected(); | ||||||
|  |         $current = $this->current(); | ||||||
|  |  | ||||||
|         $years = YearPeriod::query()->whereIn("year", $this->offset($current->year))->get(); |         $years = YearPeriod::all(); | ||||||
|         $navigation = $years->map(fn(YearPeriod $yearPeriod) => $this->toNavigation($yearPeriod)); |  | ||||||
|  |         $navigation = $years->map(fn(YearPeriod $yearPeriod): array => $this->toNavigation($yearPeriod)); | ||||||
|  |  | ||||||
|         return [ |         return [ | ||||||
|             "current" => $current->year, |             "current" => $this->toNavigation($current), | ||||||
|  |             "selected" => $this->toNavigation($selected), | ||||||
|             "navigation" => $navigation->toArray(), |             "navigation" => $navigation->toArray(), | ||||||
|         ]; |         ]; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function offset(int $year): array |  | ||||||
|     { |  | ||||||
|         return range($year - 2, $year + 2); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected function toNavigation(YearPeriod $yearPeriod): array |     protected function toNavigation(YearPeriod $yearPeriod): array | ||||||
|     { |     { | ||||||
|         return [ |         return [ | ||||||
|   | |||||||
| @@ -21,7 +21,6 @@ class Holiday extends Model | |||||||
|     use HasFactory; |     use HasFactory; | ||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         "date" => "date", |         "date" => "date", | ||||||
|     ]; |     ]; | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								app/Eloquent/Models/Key.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/Eloquent/Models/Key.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Database\Factories\KeyFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Illuminate\Notifications\Notifiable; | ||||||
|  | use Toby\Domain\Notifications\Notifiable as NotifiableInterface; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property int $id | ||||||
|  |  * @property User $user | ||||||
|  |  */ | ||||||
|  | class Key extends Model implements NotifiableInterface | ||||||
|  | { | ||||||
|  |     use HasFactory; | ||||||
|  |     use Notifiable; | ||||||
|  |  | ||||||
|  |     protected $guarded = []; | ||||||
|  |  | ||||||
|  |     public function user(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(User::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function routeNotificationForSlack(): string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.default_channel"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected static function newFactory(): KeyFactory | ||||||
|  |     { | ||||||
|  |         return KeyFactory::new(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								app/Eloquent/Models/Profile.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								app/Eloquent/Models/Profile.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Database\Factories\ProfileFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Rackbeat\UIAvatars\HasAvatar; | ||||||
|  | use Toby\Domain\Enums\EmploymentForm; | ||||||
|  | use Toby\Eloquent\Helpers\ColorGenerator; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property string $first_name | ||||||
|  |  * @property string $last_name | ||||||
|  |  * @property string $position | ||||||
|  |  * @property EmploymentForm $employment_form | ||||||
|  |  * @property Carbon $employment_date | ||||||
|  |  * @property Carbon $birthday | ||||||
|  |  */ | ||||||
|  | class Profile extends Model | ||||||
|  | { | ||||||
|  |     use HasFactory; | ||||||
|  |     use HasAvatar; | ||||||
|  |  | ||||||
|  |     protected $primaryKey = "user_id"; | ||||||
|  |     protected $guarded = []; | ||||||
|  |     protected $casts = [ | ||||||
|  |         "employment_form" => EmploymentForm::class, | ||||||
|  |         "employment_date" => "date", | ||||||
|  |         "birthday" => "date", | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     public function user(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(User::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getAvatar(): string | ||||||
|  |     { | ||||||
|  |         return $this->getAvatarGenerator() | ||||||
|  |             ->backgroundColor(ColorGenerator::generate($this->full_name)) | ||||||
|  |             ->image(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function getfullNameAttribute(): string | ||||||
|  |     { | ||||||
|  |         return "{$this->first_name} {$this->last_name}"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getAvatarName(): string | ||||||
|  |     { | ||||||
|  |         return mb_substr($this->first_name, 0, 1) . mb_substr($this->last_name, 0, 1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected static function newFactory(): ProfileFactory | ||||||
|  |     { | ||||||
|  |         return ProfileFactory::new(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								app/Eloquent/Models/Resume.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/Eloquent/Models/Resume.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Database\Factories\ResumeFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Casts\AsCollection; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property int $id | ||||||
|  |  * @property ?User $user | ||||||
|  |  * @property string $name | ||||||
|  |  * @property Collection $education | ||||||
|  |  * @property Collection $languages | ||||||
|  |  * @property Collection $technologies | ||||||
|  |  * @property Collection $projects | ||||||
|  |  */ | ||||||
|  | class Resume extends Model | ||||||
|  | { | ||||||
|  |     use HasFactory; | ||||||
|  |  | ||||||
|  |     protected $guarded = []; | ||||||
|  |     protected $casts = [ | ||||||
|  |         "education" => AsCollection::class, | ||||||
|  |         "languages" => AsCollection::class, | ||||||
|  |         "technologies" => AsCollection::class, | ||||||
|  |         "projects" => AsCollection::class, | ||||||
|  |     ]; | ||||||
|  |     protected $perPage = 50; | ||||||
|  |  | ||||||
|  |     public function user(): BelongsTo | ||||||
|  |     { | ||||||
|  |         return $this->belongsTo(User::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected static function newFactory(): ResumeFactory | ||||||
|  |     { | ||||||
|  |         return ResumeFactory::new(); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								app/Eloquent/Models/Technology.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								app/Eloquent/Models/Technology.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Database\Factories\TechnologyFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
|  | use Illuminate\Database\Eloquent\Model; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @property int $id | ||||||
|  |  * @property string $name | ||||||
|  |  */ | ||||||
|  | class Technology extends Model | ||||||
|  | { | ||||||
|  |     use HasFactory; | ||||||
|  |  | ||||||
|  |     protected $guarded = []; | ||||||
|  |  | ||||||
|  |     protected static function newFactory(): TechnologyFactory | ||||||
|  |     { | ||||||
|  |         return TechnologyFactory::new(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -8,47 +8,50 @@ use Database\Factories\UserFactory; | |||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
|  | use Illuminate\Database\Eloquent\Relations\HasOne; | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| use Illuminate\Foundation\Auth\User as Authenticatable; | use Illuminate\Foundation\Auth\User as Authenticatable; | ||||||
| use Illuminate\Notifications\Notifiable; | use Illuminate\Notifications\Notifiable; | ||||||
| use Illuminate\Support\Carbon; |  | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Rackbeat\UIAvatars\HasAvatar; |  | ||||||
| use Toby\Domain\Enums\EmploymentForm; | use Toby\Domain\Enums\EmploymentForm; | ||||||
| use Toby\Domain\Enums\Role; | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\Notifiable as NotifiableInterface; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
|  * @property string $first_name |  | ||||||
|  * @property string $last_name |  | ||||||
|  * @property string $email |  * @property string $email | ||||||
|  * @property string $avatar |  * @property string $password | ||||||
|  * @property string $position |  | ||||||
|  * @property Role $role |  * @property Role $role | ||||||
|  * @property EmploymentForm $employment_form |  * @property Profile $profile | ||||||
|  * @property Carbon $employment_date |  | ||||||
|  * @property Collection $vacationLimits |  * @property Collection $vacationLimits | ||||||
|  * @property Collection $vacationRequests |  * @property Collection $vacationRequests | ||||||
|  * @property Collection $vacations |  * @property Collection $vacations | ||||||
|  */ |  */ | ||||||
| class User extends Authenticatable | class User extends Authenticatable implements NotifiableInterface | ||||||
| { | { | ||||||
|     use HasFactory; |     use HasFactory; | ||||||
|     use Notifiable; |     use Notifiable; | ||||||
|     use SoftDeletes; |     use SoftDeletes; | ||||||
|     use HasAvatar; |  | ||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         "role" => Role::class, |         "role" => Role::class, | ||||||
|  |         "last_active_at" => "datetime", | ||||||
|         "employment_form" => EmploymentForm::class, |         "employment_form" => EmploymentForm::class, | ||||||
|         "employment_date" => "date", |         "employment_date" => "date", | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     protected $hidden = [ |     protected $hidden = [ | ||||||
|         "remember_token", |         "remember_token", | ||||||
|     ]; |     ]; | ||||||
|  |     protected $with = [ | ||||||
|  |         "profile", | ||||||
|  |     ]; | ||||||
|  |     protected $perPage = 50; | ||||||
|  |  | ||||||
|  |     public function profile(): HasOne | ||||||
|  |     { | ||||||
|  |         return $this->hasOne(Profile::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function vacationLimits(): HasMany |     public function vacationLimits(): HasMany | ||||||
|     { |     { | ||||||
| @@ -70,30 +73,9 @@ class User extends Authenticatable | |||||||
|         return $this->hasMany(Vacation::class); |         return $this->hasMany(Vacation::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function scopeSearch(Builder $query, ?string $text): Builder |     public function keys(): HasMany | ||||||
|     { |     { | ||||||
|         if ($text === null) { |         return $this->hasMany(Key::class); | ||||||
|             return $query; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return $query |  | ||||||
|             ->where("first_name", "ILIKE", $text) |  | ||||||
|             ->orWhere("last_name", "ILIKE", $text) |  | ||||||
|             ->orWhere("email", "ILIKE", $text); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public function getAvatar(): string |  | ||||||
|     { |  | ||||||
|         $colors = config("colors"); |  | ||||||
|  |  | ||||||
|         return $this->getAvatarGenerator() |  | ||||||
|             ->backgroundColor($colors[strlen($this->fullname) % count($colors)]) |  | ||||||
|             ->image(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     public function getFullNameAttribute(): string |  | ||||||
|     { |  | ||||||
|         return "{$this->first_name} {$this->last_name}"; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function hasRole(Role $role): bool |     public function hasRole(Role $role): bool | ||||||
| @@ -101,9 +83,50 @@ class User extends Authenticatable | |||||||
|         return $this->role === $role; |         return $this->role === $role; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected function getAvatarNameKey(): string |     public function hasVacationLimit(YearPeriod $yearPeriod): bool | ||||||
|     { |     { | ||||||
|         return "fullName"; |         return $this->vacationLimits() | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->whereNotNull("days") | ||||||
|  |             ->exists(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopeSearch(Builder $query, ?string $text): Builder | ||||||
|  |     { | ||||||
|  |         if ($text === null) { | ||||||
|  |             return $query; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return $query | ||||||
|  |             ->where("email", "ILIKE", "%{$text}%") | ||||||
|  |             ->orWhereRelation( | ||||||
|  |                 "profile", | ||||||
|  |                 fn(Builder $query): Builder => $query | ||||||
|  |                     ->where("first_name", "ILIKE", "%{$text}%") | ||||||
|  |                     ->orWhere("last_name", "ILIKE", "%{$text}%"), | ||||||
|  |             ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopeOrderByProfileField(Builder $query, string $field): Builder | ||||||
|  |     { | ||||||
|  |         $profileQuery = Profile::query()->select($field)->whereColumn("users.id", "profiles.user_id"); | ||||||
|  |  | ||||||
|  |         return $query->orderBy($profileQuery); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopeWithVacationLimitIn(Builder $query, YearPeriod $yearPeriod): Builder | ||||||
|  |     { | ||||||
|  |         return $query->whereRelation( | ||||||
|  |             "vacationlimits", | ||||||
|  |             fn(Builder $query): Builder => $query | ||||||
|  |                 ->whereBelongsTo($yearPeriod) | ||||||
|  |                 ->whereNotNull("days"), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function routeNotificationForSlack() | ||||||
|  |     { | ||||||
|  |         return $this->profile->slack_id; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     protected static function newFactory(): UserFactory |     protected static function newFactory(): UserFactory | ||||||
|   | |||||||
| @@ -4,15 +4,17 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Eloquent\Models; | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
|  | use Illuminate\Database\Eloquent\Builder; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
|  * @property Carbon $date |  * @property Carbon $date | ||||||
|  * @property string $event_id |  | ||||||
|  * @property User $user |  * @property User $user | ||||||
|  * @property VacationRequest $vacationRequest |  * @property VacationRequest $vacationRequest | ||||||
|  * @property YearPeriod $yearPeriod |  * @property YearPeriod $yearPeriod | ||||||
| @@ -41,4 +43,28 @@ class Vacation extends Model | |||||||
|     { |     { | ||||||
|         return $this->belongsTo(YearPeriod::class); |         return $this->belongsTo(YearPeriod::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function scopeApproved(Builder $query): Builder | ||||||
|  |     { | ||||||
|  |         return $query->whereRelation( | ||||||
|  |             "vacationRequest", | ||||||
|  |             fn(Builder $query): Builder => $query->states(VacationRequestStatesRetriever::successStates()), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopePending(Builder $query): Builder | ||||||
|  |     { | ||||||
|  |         return $query->whereRelation( | ||||||
|  |             "vacationRequest", | ||||||
|  |             fn(Builder $query): Builder => $query->states(VacationRequestStatesRetriever::pendingStates()), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     public function scopeWhereTypes(Builder $query, Collection $types): Builder | ||||||
|  |     { | ||||||
|  |         return $query->whereRelation( | ||||||
|  |             "vacationRequest", | ||||||
|  |             fn(Builder $query): Builder => $query->whereIn("type", $types), | ||||||
|  |         ); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ declare(strict_types=1); | |||||||
| namespace Toby\Eloquent\Models; | namespace Toby\Eloquent\Models; | ||||||
|  |  | ||||||
| use Database\Factories\VacationLimitFactory; | use Database\Factories\VacationLimitFactory; | ||||||
| use Illuminate\Database\Eloquent\Builder; |  | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
| @@ -37,13 +36,6 @@ class VacationLimit extends Model | |||||||
|         return $this->belongsTo(YearPeriod::class); |         return $this->belongsTo(YearPeriod::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public function scopeOrderByUserField(Builder $query, string $field): Builder |  | ||||||
|     { |  | ||||||
|         $userQuery = User::query()->select($field)->whereColumn("vacation_limits.user_id", "users.id"); |  | ||||||
|  |  | ||||||
|         return $query->orderBy($userQuery); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     protected static function newFactory(): VacationLimitFactory |     protected static function newFactory(): VacationLimitFactory | ||||||
|     { |     { | ||||||
|         return VacationLimitFactory::new(); |         return VacationLimitFactory::new(); | ||||||
|   | |||||||
| @@ -6,10 +6,12 @@ namespace Toby\Eloquent\Models; | |||||||
|  |  | ||||||
| use Database\Factories\VacationRequestFactory; | use Database\Factories\VacationRequestFactory; | ||||||
| use Illuminate\Database\Eloquent\Builder; | use Illuminate\Database\Eloquent\Builder; | ||||||
|  | use Illuminate\Database\Eloquent\Casts\AsCollection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Model; | use Illuminate\Database\Eloquent\Model; | ||||||
| use Illuminate\Database\Eloquent\Relations\BelongsTo; | use Illuminate\Database\Eloquent\Relations\BelongsTo; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | use Illuminate\Database\Eloquent\Relations\HasMany; | ||||||
|  | use Illuminate\Support\Arr; | ||||||
| use Illuminate\Support\Carbon; | use Illuminate\Support\Carbon; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Spatie\ModelStates\HasStates; | use Spatie\ModelStates\HasStates; | ||||||
| @@ -18,6 +20,7 @@ use Toby\Domain\States\VacationRequest\VacationRequestState; | |||||||
|  |  | ||||||
| /** | /** | ||||||
|  * @property int $id |  * @property int $id | ||||||
|  |  * @property string $name | ||||||
|  * @property VacationType $type |  * @property VacationType $type | ||||||
|  * @property VacationRequestState $state |  * @property VacationRequestState $state | ||||||
|  * @property Carbon $from |  * @property Carbon $from | ||||||
| @@ -29,6 +32,7 @@ use Toby\Domain\States\VacationRequest\VacationRequestState; | |||||||
|  * @property YearPeriod $yearPeriod |  * @property YearPeriod $yearPeriod | ||||||
|  * @property Collection $activities |  * @property Collection $activities | ||||||
|  * @property Collection $vacations |  * @property Collection $vacations | ||||||
|  |  * @property Collection $event_ids | ||||||
|  * @property Carbon $created_at |  * @property Carbon $created_at | ||||||
|  * @property Carbon $updated_at |  * @property Carbon $updated_at | ||||||
|  */ |  */ | ||||||
| @@ -38,13 +42,14 @@ class VacationRequest extends Model | |||||||
|     use HasStates; |     use HasStates; | ||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         "type" => VacationType::class, |         "type" => VacationType::class, | ||||||
|         "state" => VacationRequestState::class, |         "state" => VacationRequestState::class, | ||||||
|         "from" => "date", |         "from" => "date", | ||||||
|         "to" => "date", |         "to" => "date", | ||||||
|  |         "event_ids" => AsCollection::class, | ||||||
|     ]; |     ]; | ||||||
|  |     protected $perPage = 50; | ||||||
|  |  | ||||||
|     public function user(): BelongsTo |     public function user(): BelongsTo | ||||||
|     { |     { | ||||||
| @@ -81,6 +86,13 @@ class VacationRequest extends Model | |||||||
|         return $query->whereNotState("state", $states); |         return $query->whereNotState("state", $states); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function scopeType(Builder $query, VacationType|array $types): Builder | ||||||
|  |     { | ||||||
|  |         $types = Arr::wrap($types); | ||||||
|  |  | ||||||
|  |         return $query->whereIn("type", $types); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder |     public function scopeOverlapsWith(Builder $query, self $vacationRequest): Builder | ||||||
|     { |     { | ||||||
|         return $query->where("from", "<=", $vacationRequest->to) |         return $query->where("from", "<=", $vacationRequest->to) | ||||||
|   | |||||||
| @@ -22,7 +22,6 @@ class VacationRequestActivity extends Model | |||||||
|     use HasFactory; |     use HasFactory; | ||||||
|  |  | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
|  |  | ||||||
|     protected $casts = [ |     protected $casts = [ | ||||||
|         "from" => VacationRequestState::class, |         "from" => VacationRequestState::class, | ||||||
|         "to" => VacationRequestState::class, |         "to" => VacationRequestState::class, | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ use Illuminate\Support\Collection; | |||||||
|  * @property int $id |  * @property int $id | ||||||
|  * @property int $year |  * @property int $year | ||||||
|  * @property Collection $vacationLimits |  * @property Collection $vacationLimits | ||||||
|  |  * @property Collection $vacationRequests | ||||||
|  * @property Collection $holidays |  * @property Collection $holidays | ||||||
|  */ |  */ | ||||||
| class YearPeriod extends Model | class YearPeriod extends Model | ||||||
| @@ -41,6 +42,11 @@ class YearPeriod extends Model | |||||||
|         return $this->hasMany(VacationLimit::class); |         return $this->hasMany(VacationLimit::class); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public function vacationRequests(): HasMany | ||||||
|  |     { | ||||||
|  |         return $this->hasMany(VacationRequest::class); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public function holidays(): HasMany |     public function holidays(): HasMany | ||||||
|     { |     { | ||||||
|         return $this->hasMany(Holiday::class); |         return $this->hasMany(Holiday::class); | ||||||
|   | |||||||
| @@ -4,19 +4,22 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Eloquent\Observers; | namespace Toby\Eloquent\Observers; | ||||||
|  |  | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; | use Illuminate\Contracts\Hashing\Hasher; | ||||||
|  | use Illuminate\Support\Str; | ||||||
| use Toby\Eloquent\Models\User; | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
| class UserObserver | class UserObserver | ||||||
| { | { | ||||||
|     public function __construct( |     public function __construct( | ||||||
|         protected YearPeriodRetriever $yearPeriodRetriever, |         protected Hasher $hash, | ||||||
|     ) {} |     ) {} | ||||||
|  |  | ||||||
|     public function created(User $user): void |     public function creating(User $user): void | ||||||
|     { |     { | ||||||
|         $user->vacationLimits()->create([ |         /** | ||||||
|             "year_period_id" => $this->yearPeriodRetriever->current()->id, |          * A random password for user is generated because AuthenticateSession middleware needs a user's password | ||||||
|         ]); |          * for some checks. Users use Google to login, so they don't need to know the password (GitHub issue #84) | ||||||
|  |          */ | ||||||
|  |         $user->password = $this->hash->make(Str::random(40)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,25 +4,15 @@ declare(strict_types=1); | |||||||
|  |  | ||||||
| namespace Toby\Eloquent\Observers; | namespace Toby\Eloquent\Observers; | ||||||
|  |  | ||||||
| use Illuminate\Contracts\Auth\Factory as Auth; |  | ||||||
| use Illuminate\Contracts\Events\Dispatcher; |  | ||||||
| use Toby\Eloquent\Models\VacationRequest; | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
| class VacationRequestObserver | class VacationRequestObserver | ||||||
| { | { | ||||||
|     public function __construct( |  | ||||||
|         protected Auth $auth, |  | ||||||
|         protected Dispatcher $dispatcher, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function creating(VacationRequest $vacationRequest): void |     public function creating(VacationRequest $vacationRequest): void | ||||||
|     { |     { | ||||||
|         $year = $vacationRequest->from->year; |         $count = $vacationRequest->yearPeriod->vacationRequests()->count(); | ||||||
|  |         $number = $count + 1; | ||||||
|  |  | ||||||
|         $vacationRequestNumber = $vacationRequest->user->vacationRequests() |         $vacationRequest->name = "{$number}/{$vacationRequest->yearPeriod->year}"; | ||||||
|             ->whereYear("from", $year) |  | ||||||
|             ->count() + 1; |  | ||||||
|  |  | ||||||
|         $vacationRequest->name = "{$vacationRequestNumber}/${year}"; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,22 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Eloquent\Scopes; |  | ||||||
|  |  | ||||||
| use Illuminate\Database\Eloquent\Builder; |  | ||||||
| use Illuminate\Database\Eloquent\Model; |  | ||||||
| use Illuminate\Database\Eloquent\Scope; |  | ||||||
| use Toby\Eloquent\Helpers\YearPeriodRetriever; |  | ||||||
|  |  | ||||||
| class SelectedYearPeriodScope implements Scope |  | ||||||
| { |  | ||||||
|     public function __construct( |  | ||||||
|         protected YearPeriodRetriever $yearPeriodRetriever, |  | ||||||
|     ) {} |  | ||||||
|  |  | ||||||
|     public function apply(Builder $builder, Model $model): Builder |  | ||||||
|     { |  | ||||||
|         return $builder->where("year_period_id", $this->yearPeriodRetriever->selected()->id); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| <?php |  | ||||||
|  |  | ||||||
| declare(strict_types=1); |  | ||||||
|  |  | ||||||
| namespace Toby\Infrastructure\Console\Commands; |  | ||||||
|  |  | ||||||
| use Illuminate\Console\Command; |  | ||||||
| use Toby\Eloquent\Models\User; |  | ||||||
|  |  | ||||||
| class CreateUserCommand extends Command |  | ||||||
| { |  | ||||||
|     protected $signature = "user:create {email : an email for the user}"; |  | ||||||
|     protected $description = "Creates a user"; |  | ||||||
|  |  | ||||||
|     public function handle(): void |  | ||||||
|     { |  | ||||||
|         $email = $this->argument("email"); |  | ||||||
|  |  | ||||||
|         User::factory([ |  | ||||||
|             "email" => $email, |  | ||||||
|         ])->create(); |  | ||||||
|  |  | ||||||
|         $this->info("The user has been created"); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  |  | ||||||
|  | class MoveUserDataToProfile extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:move-user-data-to-profile"; | ||||||
|  |     protected $description = "Move user data to their profiles"; | ||||||
|  |  | ||||||
|  |     public function handle(): void | ||||||
|  |     { | ||||||
|  |         $users = User::all(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $user->profile()->updateOrCreate(["user_id" => $user->id], [ | ||||||
|  |                 "first_name" => $user->first_name, | ||||||
|  |                 "last_name" => $user->last_name, | ||||||
|  |                 "position" => $user->position, | ||||||
|  |                 "employment_form" => $user->employment_form, | ||||||
|  |                 "employment_date" => $user->employment_date, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,34 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Toby\Eloquent\Models\YearPeriod; | ||||||
|  |  | ||||||
|  | class RebuildDocumentNumberingSystem extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:rebuild-document-numbering-system"; | ||||||
|  |     protected $description = "Rebuilds the document numbering system to {number}/{year}"; | ||||||
|  |  | ||||||
|  |     public function handle(): void | ||||||
|  |     { | ||||||
|  |         $yearPeriods = YearPeriod::all(); | ||||||
|  |  | ||||||
|  |         foreach ($yearPeriods as $yearPeriod) { | ||||||
|  |             $number = 1; | ||||||
|  |  | ||||||
|  |             $vacationRequests = $yearPeriod | ||||||
|  |                 ->vacationRequests() | ||||||
|  |                 ->oldest() | ||||||
|  |                 ->get(); | ||||||
|  |  | ||||||
|  |             foreach ($vacationRequests as $vacationRequest) { | ||||||
|  |                 $vacationRequest->update(["name" => "{$number}/{$yearPeriod->year}"]); | ||||||
|  |  | ||||||
|  |                 $number++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,79 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Carbon\CarbonInterface; | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Illuminate\Support\Facades\Http; | ||||||
|  | use Toby\Domain\DailySummaryRetriever; | ||||||
|  | use Toby\Eloquent\Models\Holiday; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\AbsencesAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\BirthdaysAttachment; | ||||||
|  | use Toby\Infrastructure\Slack\Elements\RemotesAttachment; | ||||||
|  |  | ||||||
|  | class SendDailySummaryToSlack extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:slack:daily-summary {--f|force}"; | ||||||
|  |     protected $description = "Sent daily summary to Slack"; | ||||||
|  |  | ||||||
|  |     public function handle(DailySummaryRetriever $dailySummaryRetriever): void | ||||||
|  |     { | ||||||
|  |         $now = Carbon::today(); | ||||||
|  |  | ||||||
|  |         if (!$this->option("force") && !$this->shouldHandle($now)) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         $attachments = new Collection([ | ||||||
|  |             new AbsencesAttachment($dailySummaryRetriever->getAbsences($now)), | ||||||
|  |             new RemotesAttachment($dailySummaryRetriever->getRemoteDays($now)), | ||||||
|  |             new BirthdaysAttachment($dailySummaryRetriever->getBirthdays($now)), | ||||||
|  |         ]); | ||||||
|  |  | ||||||
|  |         Http::withToken($this->getSlackClientToken()) | ||||||
|  |             ->post($this->getUrl(), [ | ||||||
|  |                 "channel" => $this->getSlackChannel(), | ||||||
|  |                 "text" => __("Daily summary for day :day", ["day" => $now->toDisplayString()]), | ||||||
|  |                 "attachments" => $attachments, | ||||||
|  |             ]); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function shouldHandle(CarbonInterface $day): bool | ||||||
|  |     { | ||||||
|  |         $holidays = Holiday::query()->whereDate("date", $day)->pluck("date"); | ||||||
|  |  | ||||||
|  |         if ($day->isWeekend()) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if ($holidays->contains($day)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getUrl(): string | ||||||
|  |     { | ||||||
|  |         return "{$this->getSlackBaseUrl()}/chat.postMessage"; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackBaseUrl(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.url"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackClientToken(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.client_token"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     protected function getSlackChannel(): ?string | ||||||
|  |     { | ||||||
|  |         return config("services.slack.default_channel"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Console\Commands; | ||||||
|  |  | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Illuminate\Support\Carbon; | ||||||
|  | use Toby\Domain\Enums\Role; | ||||||
|  | use Toby\Domain\Notifications\VacationRequestsSummaryNotification; | ||||||
|  | use Toby\Domain\VacationRequestStatesRetriever; | ||||||
|  | use Toby\Eloquent\Models\User; | ||||||
|  | use Toby\Eloquent\Models\VacationRequest; | ||||||
|  |  | ||||||
|  | class SendVacationRequestSummariesToApprovers extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = "toby:send-vacation-request-reminders"; | ||||||
|  |     protected $description = "Sends vacation request reminders to approvers if they didn't approve"; | ||||||
|  |  | ||||||
|  |     public function handle(): void | ||||||
|  |     { | ||||||
|  |         $users = User::query() | ||||||
|  |             ->whereIn("role", [Role::AdministrativeApprover, Role::TechnicalApprover, Role::Administrator]) | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         foreach ($users as $user) { | ||||||
|  |             $vacationRequests = VacationRequest::query() | ||||||
|  |                 ->states(VacationRequestStatesRetriever::waitingForUserActionStates($user)) | ||||||
|  |                 ->get(); | ||||||
|  |  | ||||||
|  |             if ($vacationRequests->isNotEmpty()) { | ||||||
|  |                 $user->notify(new VacationRequestsSummaryNotification(Carbon::today(), $vacationRequests)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -0,0 +1,53 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace Toby\Infrastructure\Http\Controllers; | ||||||
|  |  | ||||||
|  | use Illuminate\Http\Request; | ||||||
|  | use Inertia\Response; | ||||||
|  | use Toby\Eloquent\Helpers\YearPeriodRetriever; | ||||||
|  | use Toby\Eloquent\Models\Holiday; | ||||||
|  | use Toby\Eloquent\Models\Vacation; | ||||||
|  | use Toby\Infrastructure\Http\Resources\SimpleVacationRequestResource; | ||||||
|  |  | ||||||
|  | class AnnualSummaryController extends Controller | ||||||
|  | { | ||||||
|  |     public function __invoke(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response | ||||||
|  |     { | ||||||
|  |         $yearPeriod = $yearPeriodRetriever->selected(); | ||||||
|  |  | ||||||
|  |         $holidays = $yearPeriod->holidays() | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         $vacations = $request->user() | ||||||
|  |             ->vacations() | ||||||
|  |             ->with("vacationRequest.vacations") | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->approved() | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         $pendingVacations = $request->user() | ||||||
|  |             ->vacations() | ||||||
|  |             ->with("vacationRequest.vacations") | ||||||
|  |             ->whereBelongsTo($yearPeriod) | ||||||
|  |             ->pending() | ||||||
|  |             ->get(); | ||||||
|  |  | ||||||
|  |         return inertia("AnnualSummary", [ | ||||||
|  |             "holidays" => $holidays->mapWithKeys( | ||||||
|  |                 fn(Holiday $holiday): array => [$holiday->date->toDateString() => $holiday->name], | ||||||
|  |             ), | ||||||
|  |             "vacations" => $vacations->mapWithKeys( | ||||||
|  |                 fn(Vacation $vacation): array => [ | ||||||
|  |                     $vacation->date->toDateString() => new SimpleVacationRequestResource($vacation->vacationRequest), | ||||||
|  |                 ], | ||||||
|  |             ), | ||||||
|  |             "pendingVacations" => $pendingVacations->mapWithKeys( | ||||||
|  |                 fn(Vacation $vacation): array => [ | ||||||
|  |                     $vacation->date->toDateString() => new SimpleVacationRequestResource($vacation->vacationRequest), | ||||||
|  |                 ], | ||||||
|  |             ), | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user