Compare commits
	
		
			83 Commits
		
	
	
		
			#17-dusk-t
			...
			5db97ebd0c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						5db97ebd0c
	
				 | 
					
					
						|||
| 
						
						
							
						
						d863422103
	
				 | 
					
					
						|||
| 
						
						
							
						
						61add6b1e8
	
				 | 
					
					
						|||
| 
						
						
							
						
						cbe70315eb
	
				 | 
					
					
						|||
| 
						
						
							
						
						62c3c11ebf
	
				 | 
					
					
						|||
| 
						
						
							
						
						af25d80d2e
	
				 | 
					
					
						|||
| 
						
						
							
						
						0ddbec1499
	
				 | 
					
					
						|||
| 
						
						
							
						
						94350f8a82
	
				 | 
					
					
						|||
| 
						
						
							
						
						0398b44141
	
				 | 
					
					
						|||
| 
						
						
							
						
						783586d3ed
	
				 | 
					
					
						|||
| 
						 | 
					902af64833 | ||
| 
						 | 
					d1047e8262 | ||
| 
						
						
							
						
						1d7b4da903
	
				 | 
					
					
						|||
| 
						
						
							
						
						94f433de6e
	
				 | 
					
					
						|||
| 
						
						
							
						
						a9c476cb8e
	
				 | 
					
					
						|||
| 
						
						
							
						
						5c3833d4cb
	
				 | 
					
					
						|||
| 
						
						
							
						
						6e627d11c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						eb644fa494
	
				 | 
					
					
						|||
| 
						
						
							
						
						4392b666e1
	
				 | 
					
					
						|||
| 
						
						
							
						
						e7c1028da1
	
				 | 
					
					
						|||
| a7bc4695a7 | |||
| 4e16f363ac | |||
| 7893282385 | |||
| db874d4395 | |||
| dcc3808428 | |||
| 2a3e65ed5f | |||
| 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 | 
@@ -19,8 +19,8 @@ DB_PASSWORD=password
 | 
			
		||||
 | 
			
		||||
BROADCAST_DRIVER=log
 | 
			
		||||
CACHE_DRIVER=array
 | 
			
		||||
QUEUE_CONNECTION=sync
 | 
			
		||||
SESSION_DRIVER=file
 | 
			
		||||
QUEUE_CONNECTION=redis
 | 
			
		||||
SESSION_DRIVER=redis
 | 
			
		||||
SESSION_LIFETIME=120
 | 
			
		||||
FILESYSTEM_DISK=local
 | 
			
		||||
MAIL_MAILER=array
 | 
			
		||||
 
 | 
			
		||||
@@ -57,6 +57,13 @@ DOCKER_INSTALL_XDEBUG=false
 | 
			
		||||
 | 
			
		||||
GOOGLE_CLIENT_ID=
 | 
			
		||||
GOOGLE_CLIENT_SECRET=
 | 
			
		||||
GOOGLE_CALENDAR_ENABLED=true
 | 
			
		||||
GOOGLE_REDIRECT=http://localhost/login/google/end
 | 
			
		||||
GOOGLE_CALENDAR_ID=
 | 
			
		||||
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"
 | 
			
		||||
 
 | 
			
		||||
@@ -13,5 +13,8 @@ module.exports = {
 | 
			
		||||
    indent: ['error', 2],
 | 
			
		||||
    'vue/html-indent': ['error', 2],
 | 
			
		||||
    '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
 | 
			
		||||
 | 
			
		||||
      - name: Run PHP linter
 | 
			
		||||
        run: composer ecs
 | 
			
		||||
        run: composer cs
 | 
			
		||||
 | 
			
		||||
      - name: Execute tests
 | 
			
		||||
        run: php artisan test --env=ci
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -15,5 +15,6 @@ Homestead.json
 | 
			
		||||
Homestead.yaml
 | 
			
		||||
npm-debug.log
 | 
			
		||||
yarn-error.log
 | 
			
		||||
google-credentials.json
 | 
			
		||||
.idea/
 | 
			
		||||
.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;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Foundation\Exceptions\Handler;
 | 
			
		||||
use Inertia\Inertia;
 | 
			
		||||
use Symfony\Component\HttpFoundation\Response;
 | 
			
		||||
use Throwable;
 | 
			
		||||
 | 
			
		||||
class ExceptionHandler extends Handler
 | 
			
		||||
{
 | 
			
		||||
@@ -13,4 +16,36 @@ class ExceptionHandler extends Handler
 | 
			
		||||
        "password",
 | 
			
		||||
        "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;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Foundation\Application;
 | 
			
		||||
use Illuminate\Notifications\ChannelManager;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Facades\Notification;
 | 
			
		||||
use Illuminate\Support\ServiceProvider;
 | 
			
		||||
use Toby\Infrastructure\Slack\Channels\SlackApiChannel;
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
    {
 | 
			
		||||
        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\Support\Facades\Gate;
 | 
			
		||||
use Toby\Domain\Enums\Role;
 | 
			
		||||
use Toby\Domain\Policies\KeyPolicy;
 | 
			
		||||
use Toby\Domain\Policies\VacationRequestPolicy;
 | 
			
		||||
use Toby\Eloquent\Models\Key;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
@@ -15,6 +17,7 @@ class AuthServiceProvider extends ServiceProvider
 | 
			
		||||
{
 | 
			
		||||
    protected $policies = [
 | 
			
		||||
        VacationRequest::class => VacationRequestPolicy::class,
 | 
			
		||||
        Key::class => KeyPolicy::class,
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    public function boot(): void
 | 
			
		||||
@@ -27,9 +30,11 @@ class AuthServiceProvider extends ServiceProvider
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Gate::define("manageUsers", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageHolidays", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageVacationLimits", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("generateTimesheet", fn(User $user) => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageUsers", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageHolidays", fn(User $user): bool => $user->role === Role::AdministrativeApprover);
 | 
			
		||||
        Gate::define("manageVacationLimits", fn(User $user): bool => $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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Architecture\Providers;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\ServiceProvider;
 | 
			
		||||
use Laravel\Dusk\Browser;
 | 
			
		||||
 | 
			
		||||
class DuskServiceProvider extends ServiceProvider
 | 
			
		||||
{
 | 
			
		||||
    public function boot(): void
 | 
			
		||||
    {
 | 
			
		||||
        Browser::macro("fillMonth", function ($month) {
 | 
			
		||||
            $this->select("div.flatpickr-calendar > div.flatpickr-months select", $month - 1);
 | 
			
		||||
            return $this;
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        Browser::macro("fillDay", function ($day) {
 | 
			
		||||
            $this->click("div.flatpickr-calendar.animate.arrowTop.arrowLeft.open > div.flatpickr-innerContainer > div > div.flatpickr-days > div > span:nth-child({$day})");
 | 
			
		||||
            return $this;
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -5,39 +5,8 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Architecture\Providers;
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
{
 | 
			
		||||
    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],
 | 
			
		||||
    ];
 | 
			
		||||
    protected $listen = [];
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,17 +7,14 @@ namespace Toby\Architecture\Providers;
 | 
			
		||||
use Illuminate\Support\ServiceProvider;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
use Toby\Eloquent\Observers\UserObserver;
 | 
			
		||||
use Toby\Eloquent\Observers\VacationRequestObserver;
 | 
			
		||||
use Toby\Eloquent\Observers\YearPeriodObserver;
 | 
			
		||||
 | 
			
		||||
class ObserverServiceProvider extends ServiceProvider
 | 
			
		||||
{
 | 
			
		||||
    public function boot(): void
 | 
			
		||||
    {
 | 
			
		||||
        User::observe(UserObserver::class);
 | 
			
		||||
        YearPeriod::observe(YearPeriodObserver::class);
 | 
			
		||||
        VacationRequest::observe(VacationRequestObserver::class);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,6 @@ class RouteServiceProvider extends ServiceProvider
 | 
			
		||||
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Eloquent\Observers;
 | 
			
		||||
namespace Toby\Domain\Actions;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\PolishHolidaysRetriever;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
 | 
			
		||||
class YearPeriodObserver
 | 
			
		||||
class CreateYearPeriodAction
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        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->createHolidaysFor($yearPeriod);
 | 
			
		||||
 | 
			
		||||
        return $yearPeriod;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
use Carbon\CarbonPeriod;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
@@ -44,6 +43,7 @@ class CalendarGenerator
 | 
			
		||||
                "isWeekend" => $day->isWeekend(),
 | 
			
		||||
                "isHoliday" => $holidays->contains($day),
 | 
			
		||||
                "vacations" => $vacationsForDay->pluck("user_id"),
 | 
			
		||||
                "vacationTypes" => $vacationsForDay->pluck("vacationRequest.type", "user_id"),
 | 
			
		||||
            ];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -54,8 +54,9 @@ class CalendarGenerator
 | 
			
		||||
    {
 | 
			
		||||
        return Vacation::query()
 | 
			
		||||
            ->whereBetween("date", [$period->start, $period->end])
 | 
			
		||||
            ->whereRelation("vacationRequest", fn(Builder $query) => $query->states(VacationRequestStatesRetriever::successStates()))
 | 
			
		||||
            ->approved()
 | 
			
		||||
            ->with("vacationRequest")
 | 
			
		||||
            ->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());
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(EmploymentForm $enum) => [
 | 
			
		||||
            fn(EmploymentForm $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "value" => $enum->value,
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ enum Role: string
 | 
			
		||||
        $cases = collect(Role::cases());
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(Role $enum) => [
 | 
			
		||||
            fn(Role $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "value" => $enum->value,
 | 
			
		||||
            ],
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Enums;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
 | 
			
		||||
enum VacationType: string
 | 
			
		||||
{
 | 
			
		||||
    case Vacation = "vacation";
 | 
			
		||||
@@ -15,6 +17,8 @@ enum VacationType: string
 | 
			
		||||
    case Volunteering = "volunteering_vacation";
 | 
			
		||||
    case TimeInLieu = "time_in_lieu";
 | 
			
		||||
    case Sick = "sick_vacation";
 | 
			
		||||
    case Absence = "absence";
 | 
			
		||||
    case HomeOffice = "home_office";
 | 
			
		||||
 | 
			
		||||
    public function label(): string
 | 
			
		||||
    {
 | 
			
		||||
@@ -23,13 +27,18 @@ enum VacationType: string
 | 
			
		||||
 | 
			
		||||
    public static function casesToSelect(): array
 | 
			
		||||
    {
 | 
			
		||||
        $cases = collect(VacationType::cases());
 | 
			
		||||
        $cases = VacationType::all();
 | 
			
		||||
 | 
			
		||||
        return $cases->map(
 | 
			
		||||
            fn(VacationType $enum) => [
 | 
			
		||||
            fn(VacationType $enum): array => [
 | 
			
		||||
                "label" => $enum->label(),
 | 
			
		||||
                "value" => $enum->value,
 | 
			
		||||
            ],
 | 
			
		||||
        )->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 InvalidArgumentException;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Infrastructure\Slack\Elements\SlackMessage;
 | 
			
		||||
 | 
			
		||||
class VacationRequestCreatedNotification extends Notification
 | 
			
		||||
{
 | 
			
		||||
@@ -20,7 +21,16 @@ class VacationRequestCreatedNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->vacationRequest->user->first_name;
 | 
			
		||||
        $title = $this->vacationRequest->name;
 | 
			
		||||
        $user = $this->vacationRequest->user->profile->first_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", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("The vacation request :title has been created correctly in the :appName.", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
                "appName" => $appName,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("Vacation type: :type", [
 | 
			
		||||
                "type" => $type,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("From :from to :to (number of days: :days)", [
 | 
			
		||||
                "from" => $from,
 | 
			
		||||
                "to" => $to,
 | 
			
		||||
                "days" => $days,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->greeting(
 | 
			
		||||
                __("Hi :user!", [
 | 
			
		||||
                    "user" => $user,
 | 
			
		||||
                ]),
 | 
			
		||||
            )
 | 
			
		||||
            ->subject($this->buildSubject())
 | 
			
		||||
            ->line($this->buildDescription())
 | 
			
		||||
            ->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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Infrastructure\Slack\Elements\SlackMessage;
 | 
			
		||||
 | 
			
		||||
class VacationRequestApprovedNotification extends Notification
 | 
			
		||||
class VacationRequestStatusChangedNotification extends Notification
 | 
			
		||||
{
 | 
			
		||||
    use Queueable;
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +23,16 @@ class VacationRequestApprovedNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->user->first_name;
 | 
			
		||||
        $title = $this->vacationRequest->name;
 | 
			
		||||
        $user = $this->user->profile->first_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 approved", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("The vacation request :title for user :requester has been approved.", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
                "requester" => $requester,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->subject($this->buildSubject())
 | 
			
		||||
            ->line($this->buildDescription())
 | 
			
		||||
            ->line(__("Vacation type: :type", [
 | 
			
		||||
                "type" => $type,
 | 
			
		||||
            ]))
 | 
			
		||||
@@ -71,4 +74,21 @@ class VacationRequestApprovedNotification extends Notification
 | 
			
		||||
            ]))
 | 
			
		||||
            ->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\Notification;
 | 
			
		||||
use InvalidArgumentException;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\WaitingForTechnical;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Infrastructure\Slack\Elements\SlackMessage;
 | 
			
		||||
 | 
			
		||||
class VacationRequestCancelledNotification extends Notification
 | 
			
		||||
class VacationRequestWaitsForApprovalNotification extends Notification
 | 
			
		||||
{
 | 
			
		||||
    use Queueable;
 | 
			
		||||
 | 
			
		||||
@@ -22,7 +24,16 @@ class VacationRequestCancelledNotification extends Notification
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        $user = $this->user->first_name;
 | 
			
		||||
        $title = $this->vacationRequest->name;
 | 
			
		||||
        $user = $this->user->profile->first_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 cancelled", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->line(__("The vacation request :title for user :requester has been cancelled.", [
 | 
			
		||||
                "title" => $title,
 | 
			
		||||
                "requester" => $requester,
 | 
			
		||||
            ]))
 | 
			
		||||
            ->subject($this->buildSubject())
 | 
			
		||||
            ->line($this->buildDescription())
 | 
			
		||||
            ->line(__("Vacation type: :type", [
 | 
			
		||||
                "type" => $type,
 | 
			
		||||
            ]))
 | 
			
		||||
@@ -71,4 +75,37 @@ class VacationRequestCancelledNotification extends Notification
 | 
			
		||||
            ]))
 | 
			
		||||
            ->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;
 | 
			
		||||
 | 
			
		||||
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\VacationRequest;
 | 
			
		||||
 | 
			
		||||
@@ -40,8 +43,16 @@ class VacationRequestPolicy
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ class PolishHolidaysRetriever
 | 
			
		||||
 | 
			
		||||
    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]),
 | 
			
		||||
            "date" => Carbon::createFromTimestamp($holiday->getTimestamp()),
 | 
			
		||||
        ])->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,
 | 
			
		||||
            ], Cancelled::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function label(): string
 | 
			
		||||
    {
 | 
			
		||||
        return __(static::$name);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,20 +4,21 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Collection;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
 | 
			
		||||
class TimesheetExport implements WithMultipleSheets
 | 
			
		||||
{
 | 
			
		||||
    protected Collection $users;
 | 
			
		||||
    protected Collection $types;
 | 
			
		||||
    protected Carbon $month;
 | 
			
		||||
 | 
			
		||||
    public function sheets(): array
 | 
			
		||||
    {
 | 
			
		||||
        return $this->users
 | 
			
		||||
            ->map(fn(User $user) => new TimesheetPerUserSheet($user, $this->month))
 | 
			
		||||
            ->map(fn(User $user): TimesheetPerUserSheet => new TimesheetPerUserSheet($user, $this->month, $this->types))
 | 
			
		||||
            ->toArray();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -34,4 +35,11 @@ class TimesheetExport implements WithMultipleSheets
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function forVacationTypes(Collection $types): static
 | 
			
		||||
    {
 | 
			
		||||
        $this->types = $types;
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ namespace Toby\Domain;
 | 
			
		||||
use Carbon\CarbonInterface;
 | 
			
		||||
use Carbon\CarbonPeriod;
 | 
			
		||||
use Generator;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Maatwebsite\Excel\Concerns\FromGenerator;
 | 
			
		||||
@@ -25,7 +26,6 @@ use PhpOffice\PhpSpreadsheet\Style\Fill;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
 | 
			
		||||
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Approved;
 | 
			
		||||
use Toby\Eloquent\Models\Holiday;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\Vacation;
 | 
			
		||||
@@ -41,17 +41,16 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected User $user,
 | 
			
		||||
        protected Carbon $month,
 | 
			
		||||
        protected Collection $types,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function title(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->user->fullName;
 | 
			
		||||
        return $this->user->profile->full_name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function headings(): array
 | 
			
		||||
    {
 | 
			
		||||
        $types = VacationType::cases();
 | 
			
		||||
 | 
			
		||||
        $headings = [
 | 
			
		||||
            __("Date"),
 | 
			
		||||
            __("Day of week"),
 | 
			
		||||
@@ -60,7 +59,7 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With
 | 
			
		||||
            __("Worked hours"),
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        foreach ($types as $type) {
 | 
			
		||||
        foreach ($this->types as $type) {
 | 
			
		||||
            $headings[] = $type->label();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -188,13 +187,14 @@ class TimesheetPerUserSheet implements WithTitle, WithHeadings, WithEvents, With
 | 
			
		||||
    {
 | 
			
		||||
        return $user->vacations()
 | 
			
		||||
            ->with("vacationRequest")
 | 
			
		||||
            ->whereRelation("vacationRequest", fn(Builder $query): Builder => $query->whereIn("type", $this->types))
 | 
			
		||||
            ->whereBetween("date", [$period->start, $period->end])
 | 
			
		||||
            ->whereRelation("vacationRequest", "state", Approved::$name)
 | 
			
		||||
            ->approved()
 | 
			
		||||
            ->get()
 | 
			
		||||
            ->groupBy(
 | 
			
		||||
                [
 | 
			
		||||
                    fn(Vacation $vacation) => $vacation->date->toDateString(),
 | 
			
		||||
                    fn(Vacation $vacation) => $vacation->vacationRequest->type->value,
 | 
			
		||||
                    fn(Vacation $vacation): string => $vacation->date->toDateString(),
 | 
			
		||||
                    fn(Vacation $vacation): string => $vacation->vacationRequest->type->value,
 | 
			
		||||
                ],
 | 
			
		||||
            );
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -5,9 +5,10 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Collection;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\Vacation;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
 | 
			
		||||
class UserVacationStatsRetriever
 | 
			
		||||
@@ -20,24 +21,39 @@ class UserVacationStatsRetriever
 | 
			
		||||
    {
 | 
			
		||||
        return $user
 | 
			
		||||
            ->vacations()
 | 
			
		||||
            ->where("year_period_id", $yearPeriod->id)
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->whereRelation(
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                fn(Builder $query): Builder => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->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
 | 
			
		||||
    {
 | 
			
		||||
        return $user
 | 
			
		||||
            ->vacations()
 | 
			
		||||
            ->where("year_period_id", $yearPeriod->id)
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->whereRelation(
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                fn(Builder $query): Builder => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::pendingStates()),
 | 
			
		||||
            )
 | 
			
		||||
@@ -48,16 +64,26 @@ class UserVacationStatsRetriever
 | 
			
		||||
    {
 | 
			
		||||
        return $user
 | 
			
		||||
            ->vacations()
 | 
			
		||||
            ->where("year_period_id", $yearPeriod->id)
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->whereRelation(
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                fn(Builder $query): Builder => $query
 | 
			
		||||
                    ->whereIn("type", $this->getNotLimitableVacationTypes())
 | 
			
		||||
                    ->whereNot("type", VacationType::HomeOffice)
 | 
			
		||||
                    ->states(VacationRequestStatesRetriever::successStates()),
 | 
			
		||||
            )
 | 
			
		||||
            ->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
 | 
			
		||||
    {
 | 
			
		||||
        $limit = $this->getVacationDaysLimit($user, $yearPeriod);
 | 
			
		||||
@@ -70,24 +96,33 @@ class UserVacationStatsRetriever
 | 
			
		||||
    public function getVacationDaysLimit(User $user, YearPeriod $yearPeriod): int
 | 
			
		||||
    {
 | 
			
		||||
        $limit = $user->vacationLimits()
 | 
			
		||||
            ->where("year_period_id", $yearPeriod->id)
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->first()
 | 
			
		||||
            ->days;
 | 
			
		||||
            ?->days;
 | 
			
		||||
 | 
			
		||||
        return $limit ?? 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasVacationDaysLimit(User $user, YearPeriod $yearPeriod): bool
 | 
			
		||||
    {
 | 
			
		||||
        $limit = $user->vacationLimits()
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->first()?->days;
 | 
			
		||||
 | 
			
		||||
        return $limit !== null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        $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;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Auth\Factory as Auth;
 | 
			
		||||
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\AcceptedByTechnical;
 | 
			
		||||
use Toby\Domain\States\VacationRequest\Approved;
 | 
			
		||||
@@ -29,63 +19,47 @@ use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
class VacationRequestStateManager
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected Auth $auth,
 | 
			
		||||
        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->dispatcher->dispatch(new VacationRequestCreated($vacationRequest));
 | 
			
		||||
        $this->createActivity($vacationRequest, null, $vacationRequest->state, $vacationRequest->creator);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function approve(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    {
 | 
			
		||||
        $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->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->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->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->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->dispatcher->dispatch(new VacationRequestWaitsForTechApproval($vacationRequest));
 | 
			
		||||
        $this->changeState($vacationRequest, WaitingForTechnical::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function waitForAdministrative(VacationRequest $vacationRequest, ?User $user = null): void
 | 
			
		||||
    public function waitForAdministrative(VacationRequest $vacationRequest): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->changeState($vacationRequest, WaitingForAdministrative::class, $user);
 | 
			
		||||
 | 
			
		||||
        $this->dispatcher->dispatch(new VacationRequestWaitsForAdminApproval($vacationRequest));
 | 
			
		||||
        $this->changeState($vacationRequest, WaitingForAdministrative::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function changeState(VacationRequest $vacationRequest, string $state, ?User $user = null): void
 | 
			
		||||
@@ -94,16 +68,19 @@ class VacationRequestStateManager
 | 
			
		||||
        $vacationRequest->state->transitionTo($state);
 | 
			
		||||
        $vacationRequest->save();
 | 
			
		||||
 | 
			
		||||
        $this->fireStateChangedEvent($vacationRequest, $previousState, $vacationRequest->state, $user);
 | 
			
		||||
        $this->createActivity($vacationRequest, $previousState, $vacationRequest->state, $user);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function fireStateChangedEvent(
 | 
			
		||||
    protected function createActivity(
 | 
			
		||||
        VacationRequest $vacationRequest,
 | 
			
		||||
        ?VacationRequestState $from,
 | 
			
		||||
        VacationRequestState $to,
 | 
			
		||||
        ?User $user = null,
 | 
			
		||||
    ): void {
 | 
			
		||||
        $event = new VacationRequestStateChanged($vacationRequest, $from, $to, $user);
 | 
			
		||||
        $this->dispatcher->dispatch($event);
 | 
			
		||||
        $vacationRequest->activities()->create([
 | 
			
		||||
            "from" => $from,
 | 
			
		||||
            "to" => $to,
 | 
			
		||||
            "user_id" => $user?->id,
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Domain;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Config\Repository;
 | 
			
		||||
use Toby\Domain\Enums\EmploymentForm;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
 | 
			
		||||
class VacationTypeConfigRetriever
 | 
			
		||||
@@ -13,6 +14,8 @@ class VacationTypeConfigRetriever
 | 
			
		||||
    public const KEY_ADMINISTRATIVE_APPROVAL = "administrative_approval";
 | 
			
		||||
    public const KEY_BILLABLE = "billable";
 | 
			
		||||
    public const KEY_HAS_LIMIT = "has_limit";
 | 
			
		||||
    public const KEY_AVAILABLE_FOR = "available_for";
 | 
			
		||||
    public const KEY_IS_VACATION = "is_vacation";
 | 
			
		||||
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected Repository $config,
 | 
			
		||||
@@ -38,6 +41,16 @@ class VacationTypeConfigRetriever
 | 
			
		||||
        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
 | 
			
		||||
    {
 | 
			
		||||
        return $this->config->get("vacation_types.{$type->value}");
 | 
			
		||||
 
 | 
			
		||||
@@ -5,11 +5,11 @@ declare(strict_types=1);
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Collection;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\Enums\VacationType;
 | 
			
		||||
use Toby\Domain\VacationDaysCalculator;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
use Toby\Domain\VacationTypeConfigRetriever;
 | 
			
		||||
use Toby\Domain\WorkDaysCalculator;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
use Toby\Eloquent\Models\YearPeriod;
 | 
			
		||||
@@ -18,7 +18,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected VacationTypeConfigRetriever $configRetriever,
 | 
			
		||||
        protected VacationDaysCalculator $vacationDaysCalculator,
 | 
			
		||||
        protected WorkDaysCalculator $workDaysCalculator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function check(VacationRequest $vacationRequest): bool
 | 
			
		||||
@@ -29,7 +29,9 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
 | 
			
		||||
        $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod);
 | 
			
		||||
        $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod);
 | 
			
		||||
        $estimatedDays = $this->vacationDaysCalculator->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)->count();
 | 
			
		||||
        $estimatedDays = $this->workDaysCalculator
 | 
			
		||||
            ->calculateDays($vacationRequest->from, $vacationRequest->to)
 | 
			
		||||
            ->count();
 | 
			
		||||
 | 
			
		||||
        return $limit >= ($vacationDays + $estimatedDays);
 | 
			
		||||
    }
 | 
			
		||||
@@ -41,16 +43,19 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
    {
 | 
			
		||||
        return $user->vacations()
 | 
			
		||||
            ->where("year_period_id", $yearPeriod->id)
 | 
			
		||||
            ->whereBelongsTo($yearPeriod)
 | 
			
		||||
            ->whereRelation(
 | 
			
		||||
                "vacationRequest",
 | 
			
		||||
                fn(Builder $query) => $query
 | 
			
		||||
                fn(Builder $query): Builder => $query
 | 
			
		||||
                    ->whereIn("type", $this->getLimitableVacationTypes())
 | 
			
		||||
                    ->noStates(VacationRequestStatesRetriever::failedStates()),
 | 
			
		||||
            )
 | 
			
		||||
@@ -59,8 +64,8 @@ class DoesNotExceedLimitRule implements VacationRequestRule
 | 
			
		||||
 | 
			
		||||
    protected function getLimitableVacationTypes(): Collection
 | 
			
		||||
    {
 | 
			
		||||
        $types = new Collection(VacationType::cases());
 | 
			
		||||
        $types = VacationType::all();
 | 
			
		||||
 | 
			
		||||
        return $types->filter(fn(VacationType $type) => $this->configRetriever->hasLimit($type));
 | 
			
		||||
        return $types->filter(fn(VacationType $type): bool => $this->configRetriever->hasLimit($type));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,19 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Domain\Validation\Rules;
 | 
			
		||||
 | 
			
		||||
use Toby\Domain\VacationDaysCalculator;
 | 
			
		||||
use Toby\Domain\WorkDaysCalculator;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class MinimumOneVacationDayRule implements VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected VacationDaysCalculator $vacationDaysCalculator,
 | 
			
		||||
        protected WorkDaysCalculator $workDaysCalculator,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function check(VacationRequest $vacationRequest): bool
 | 
			
		||||
    {
 | 
			
		||||
        return $this->vacationDaysCalculator
 | 
			
		||||
            ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to)
 | 
			
		||||
        return $this->workDaysCalculator
 | 
			
		||||
            ->calculateDays($vacationRequest->from, $vacationRequest->to)
 | 
			
		||||
            ->isNotEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,5 +9,6 @@ use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
interface VacationRequestRule
 | 
			
		||||
{
 | 
			
		||||
    public function check(VacationRequest $vacationRequest): bool;
 | 
			
		||||
 | 
			
		||||
    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\VacationRangeIsInTheSameYearRule;
 | 
			
		||||
use Toby\Domain\Validation\Rules\VacationRequestRule;
 | 
			
		||||
use Toby\Domain\Validation\Rules\VacationTypeCanBeSelected;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestValidator
 | 
			
		||||
@@ -19,6 +20,7 @@ class VacationRequestValidator
 | 
			
		||||
    protected array $rules = [
 | 
			
		||||
        VacationRangeIsInTheSameYearRule::class,
 | 
			
		||||
        MinimumOneVacationDayRule::class,
 | 
			
		||||
        VacationTypeCanBeSelected::class,
 | 
			
		||||
        DoesNotExceedLimitRule::class,
 | 
			
		||||
        NoPendingVacationRequestInRange::class,
 | 
			
		||||
        NoApprovedVacationRequestsInRange::class,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,12 @@ use Carbon\CarbonPeriod;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
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);
 | 
			
		||||
        $yearPeriod = YearPeriod::findByYear($from->year);
 | 
			
		||||
        $holidays = $yearPeriod->holidays()->pluck("date");
 | 
			
		||||
 | 
			
		||||
        $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
 | 
			
		||||
    {
 | 
			
		||||
        $current = $this->selected();
 | 
			
		||||
        $selected = $this->selected();
 | 
			
		||||
        $current = $this->current();
 | 
			
		||||
 | 
			
		||||
        $years = YearPeriod::query()->whereIn("year", $this->offset($current->year))->get();
 | 
			
		||||
        $navigation = $years->map(fn(YearPeriod $yearPeriod) => $this->toNavigation($yearPeriod));
 | 
			
		||||
        $years = YearPeriod::all();
 | 
			
		||||
 | 
			
		||||
        $navigation = $years->map(fn(YearPeriod $yearPeriod): array => $this->toNavigation($yearPeriod));
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            "current" => $current->year,
 | 
			
		||||
            "current" => $this->toNavigation($current),
 | 
			
		||||
            "selected" => $this->toNavigation($selected),
 | 
			
		||||
            "navigation" => $navigation->toArray(),
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function offset(int $year): array
 | 
			
		||||
    {
 | 
			
		||||
        return range($year - 2, $year + 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function toNavigation(YearPeriod $yearPeriod): array
 | 
			
		||||
    {
 | 
			
		||||
        return [
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ class Holiday extends Model
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        "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\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasMany;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\HasOne;
 | 
			
		||||
use Illuminate\Database\Eloquent\SoftDeletes;
 | 
			
		||||
use Illuminate\Foundation\Auth\User as Authenticatable;
 | 
			
		||||
use Illuminate\Notifications\Notifiable;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Rackbeat\UIAvatars\HasAvatar;
 | 
			
		||||
use Toby\Domain\Enums\EmploymentForm;
 | 
			
		||||
use Toby\Domain\Enums\Role;
 | 
			
		||||
use Toby\Domain\Notifications\Notifiable as NotifiableInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property string $first_name
 | 
			
		||||
 * @property string $last_name
 | 
			
		||||
 * @property string $email
 | 
			
		||||
 * @property string $avatar
 | 
			
		||||
 * @property string $position
 | 
			
		||||
 * @property string $password
 | 
			
		||||
 * @property Role $role
 | 
			
		||||
 * @property EmploymentForm $employment_form
 | 
			
		||||
 * @property Carbon $employment_date
 | 
			
		||||
 * @property Profile $profile
 | 
			
		||||
 * @property Collection $vacationLimits
 | 
			
		||||
 * @property Collection $vacationRequests
 | 
			
		||||
 * @property Collection $vacations
 | 
			
		||||
 */
 | 
			
		||||
class User extends Authenticatable
 | 
			
		||||
class User extends Authenticatable implements NotifiableInterface
 | 
			
		||||
{
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
    use Notifiable;
 | 
			
		||||
    use SoftDeletes;
 | 
			
		||||
    use HasAvatar;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        "role" => Role::class,
 | 
			
		||||
        "last_active_at" => "datetime",
 | 
			
		||||
        "employment_form" => EmploymentForm::class,
 | 
			
		||||
        "employment_date" => "date",
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    protected $hidden = [
 | 
			
		||||
        "remember_token",
 | 
			
		||||
    ];
 | 
			
		||||
    protected $with = [
 | 
			
		||||
        "profile",
 | 
			
		||||
    ];
 | 
			
		||||
    protected $perPage = 50;
 | 
			
		||||
 | 
			
		||||
    public function profile(): HasOne
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasOne(Profile::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function vacationLimits(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
@@ -70,30 +73,9 @@ class User extends Authenticatable
 | 
			
		||||
        return $this->hasMany(Vacation::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function scopeSearch(Builder $query, ?string $text): Builder
 | 
			
		||||
    public function keys(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        if ($text === null) {
 | 
			
		||||
            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}";
 | 
			
		||||
        return $this->hasMany(Key::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function hasRole(Role $role): bool
 | 
			
		||||
@@ -101,9 +83,50 @@ class User extends Authenticatable
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,17 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Eloquent\Models;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Toby\Domain\VacationRequestStatesRetriever;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property Carbon $date
 | 
			
		||||
 * @property string $event_id
 | 
			
		||||
 * @property User $user
 | 
			
		||||
 * @property VacationRequest $vacationRequest
 | 
			
		||||
 * @property YearPeriod $yearPeriod
 | 
			
		||||
@@ -41,4 +43,28 @@ class Vacation extends Model
 | 
			
		||||
    {
 | 
			
		||||
        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;
 | 
			
		||||
 | 
			
		||||
use Database\Factories\VacationLimitFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Model;
 | 
			
		||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 | 
			
		||||
@@ -37,13 +36,6 @@ class VacationLimit extends Model
 | 
			
		||||
        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
 | 
			
		||||
    {
 | 
			
		||||
        return VacationLimitFactory::new();
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,12 @@ namespace Toby\Eloquent\Models;
 | 
			
		||||
 | 
			
		||||
use Database\Factories\VacationRequestFactory;
 | 
			
		||||
use Illuminate\Database\Eloquent\Builder;
 | 
			
		||||
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\Database\Eloquent\Relations\HasMany;
 | 
			
		||||
use Illuminate\Support\Arr;
 | 
			
		||||
use Illuminate\Support\Carbon;
 | 
			
		||||
use Illuminate\Support\Collection;
 | 
			
		||||
use Spatie\ModelStates\HasStates;
 | 
			
		||||
@@ -18,6 +20,7 @@ use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property string $name
 | 
			
		||||
 * @property VacationType $type
 | 
			
		||||
 * @property VacationRequestState $state
 | 
			
		||||
 * @property Carbon $from
 | 
			
		||||
@@ -29,6 +32,7 @@ use Toby\Domain\States\VacationRequest\VacationRequestState;
 | 
			
		||||
 * @property YearPeriod $yearPeriod
 | 
			
		||||
 * @property Collection $activities
 | 
			
		||||
 * @property Collection $vacations
 | 
			
		||||
 * @property Collection $event_ids
 | 
			
		||||
 * @property Carbon $created_at
 | 
			
		||||
 * @property Carbon $updated_at
 | 
			
		||||
 */
 | 
			
		||||
@@ -38,13 +42,14 @@ class VacationRequest extends Model
 | 
			
		||||
    use HasStates;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        "type" => VacationType::class,
 | 
			
		||||
        "state" => VacationRequestState::class,
 | 
			
		||||
        "from" => "date",
 | 
			
		||||
        "to" => "date",
 | 
			
		||||
        "event_ids" => AsCollection::class,
 | 
			
		||||
    ];
 | 
			
		||||
    protected $perPage = 50;
 | 
			
		||||
 | 
			
		||||
    public function user(): BelongsTo
 | 
			
		||||
    {
 | 
			
		||||
@@ -81,6 +86,13 @@ class VacationRequest extends Model
 | 
			
		||||
        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
 | 
			
		||||
    {
 | 
			
		||||
        return $query->where("from", "<=", $vacationRequest->to)
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,6 @@ class VacationRequestActivity extends Model
 | 
			
		||||
    use HasFactory;
 | 
			
		||||
 | 
			
		||||
    protected $guarded = [];
 | 
			
		||||
 | 
			
		||||
    protected $casts = [
 | 
			
		||||
        "from" => VacationRequestState::class,
 | 
			
		||||
        "to" => VacationRequestState::class,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ use Illuminate\Support\Collection;
 | 
			
		||||
 * @property int $id
 | 
			
		||||
 * @property int $year
 | 
			
		||||
 * @property Collection $vacationLimits
 | 
			
		||||
 * @property Collection $vacationRequests
 | 
			
		||||
 * @property Collection $holidays
 | 
			
		||||
 */
 | 
			
		||||
class YearPeriod extends Model
 | 
			
		||||
@@ -41,6 +42,11 @@ class YearPeriod extends Model
 | 
			
		||||
        return $this->hasMany(VacationLimit::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function vacationRequests(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(VacationRequest::class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function holidays(): HasMany
 | 
			
		||||
    {
 | 
			
		||||
        return $this->hasMany(Holiday::class);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,19 +4,22 @@ declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Toby\Eloquent\Observers;
 | 
			
		||||
 | 
			
		||||
use Toby\Eloquent\Helpers\YearPeriodRetriever;
 | 
			
		||||
use Illuminate\Contracts\Hashing\Hasher;
 | 
			
		||||
use Illuminate\Support\Str;
 | 
			
		||||
use Toby\Eloquent\Models\User;
 | 
			
		||||
 | 
			
		||||
class UserObserver
 | 
			
		||||
{
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
use Illuminate\Contracts\Auth\Factory as Auth;
 | 
			
		||||
use Illuminate\Contracts\Events\Dispatcher;
 | 
			
		||||
use Toby\Eloquent\Models\VacationRequest;
 | 
			
		||||
 | 
			
		||||
class VacationRequestObserver
 | 
			
		||||
{
 | 
			
		||||
    public function __construct(
 | 
			
		||||
        protected Auth $auth,
 | 
			
		||||
        protected Dispatcher $dispatcher,
 | 
			
		||||
    ) {}
 | 
			
		||||
 | 
			
		||||
    public function creating(VacationRequest $vacationRequest): void
 | 
			
		||||
    {
 | 
			
		||||
        $year = $vacationRequest->from->year;
 | 
			
		||||
        $count = $vacationRequest->yearPeriod->vacationRequests()->count();
 | 
			
		||||
        $number = $count + 1;
 | 
			
		||||
 | 
			
		||||
        $vacationRequestNumber = $vacationRequest->user->vacationRequests()
 | 
			
		||||
            ->whereYear("from", $year)
 | 
			
		||||
            ->count() + 1;
 | 
			
		||||
 | 
			
		||||
        $vacationRequest->name = "{$vacationRequestNumber}/${year}";
 | 
			
		||||
        $vacationRequest->name = "{$number}/{$vacationRequest->yearPeriod->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");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user