From dcce18841940c58c6c27311b57486e22386a434d Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Mon, 25 Apr 2022 15:18:48 +0200 Subject: [PATCH 01/15] #126 - vacation request reminders --- app/Architecture/ExceptionHandler.php | 1 - ...ionRequestWaitsForApprovalNotification.php | 1 - .../Validation/Rules/VacationRequestRule.php | 1 + app/Eloquent/Models/Holiday.php | 1 - app/Eloquent/Models/Profile.php | 2 - app/Eloquent/Models/User.php | 3 - app/Eloquent/Models/VacationRequest.php | 9 +- .../Models/VacationRequestActivity.php | 1 - ...endVacationRequestRemindersToApprovers.php | 76 ++++++++++ app/Infrastructure/Http/Kernel.php | 2 - .../Unit/SendVacationRequestRemindersTest.php | 142 ++++++++++++++++++ 11 files changed, 227 insertions(+), 12 deletions(-) create mode 100644 app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php create mode 100644 tests/Unit/SendVacationRequestRemindersTest.php diff --git a/app/Architecture/ExceptionHandler.php b/app/Architecture/ExceptionHandler.php index b286bd1..a43fcab 100644 --- a/app/Architecture/ExceptionHandler.php +++ b/app/Architecture/ExceptionHandler.php @@ -16,7 +16,6 @@ class ExceptionHandler extends Handler "password", "password_confirmation", ]; - protected array $handleByInertia = [ Response::HTTP_INTERNAL_SERVER_ERROR, Response::HTTP_SERVICE_UNAVAILABLE, diff --git a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php index 109eef9..90b5507 100644 --- a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php +++ b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php @@ -99,4 +99,3 @@ class VacationRequestWaitsForApprovalNotification extends Notification ]); } } - diff --git a/app/Domain/Validation/Rules/VacationRequestRule.php b/app/Domain/Validation/Rules/VacationRequestRule.php index 07af8d2..f7e3c6d 100644 --- a/app/Domain/Validation/Rules/VacationRequestRule.php +++ b/app/Domain/Validation/Rules/VacationRequestRule.php @@ -9,5 +9,6 @@ use Toby\Eloquent\Models\VacationRequest; interface VacationRequestRule { public function check(VacationRequest $vacationRequest): bool; + public function errorMessage(): string; } diff --git a/app/Eloquent/Models/Holiday.php b/app/Eloquent/Models/Holiday.php index be8ae49..466065c 100644 --- a/app/Eloquent/Models/Holiday.php +++ b/app/Eloquent/Models/Holiday.php @@ -21,7 +21,6 @@ class Holiday extends Model use HasFactory; protected $guarded = []; - protected $casts = [ "date" => "date", ]; diff --git a/app/Eloquent/Models/Profile.php b/app/Eloquent/Models/Profile.php index df237e9..b8b3dc4 100644 --- a/app/Eloquent/Models/Profile.php +++ b/app/Eloquent/Models/Profile.php @@ -26,9 +26,7 @@ class Profile extends Model use HasAvatar; protected $primaryKey = "user_id"; - protected $guarded = []; - protected $casts = [ "employment_form" => EmploymentForm::class, "employment_date" => "date", diff --git a/app/Eloquent/Models/User.php b/app/Eloquent/Models/User.php index 4bf2891..24bb49c 100644 --- a/app/Eloquent/Models/User.php +++ b/app/Eloquent/Models/User.php @@ -33,18 +33,15 @@ class User extends Authenticatable use SoftDeletes; 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", ]; diff --git a/app/Eloquent/Models/VacationRequest.php b/app/Eloquent/Models/VacationRequest.php index 46bd084..cb77f1c 100644 --- a/app/Eloquent/Models/VacationRequest.php +++ b/app/Eloquent/Models/VacationRequest.php @@ -11,6 +11,7 @@ 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; @@ -41,7 +42,6 @@ class VacationRequest extends Model use HasStates; protected $guarded = []; - protected $casts = [ "type" => VacationType::class, "state" => VacationRequestState::class, @@ -85,6 +85,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) diff --git a/app/Eloquent/Models/VacationRequestActivity.php b/app/Eloquent/Models/VacationRequestActivity.php index 942bb96..2064e9e 100644 --- a/app/Eloquent/Models/VacationRequestActivity.php +++ b/app/Eloquent/Models/VacationRequestActivity.php @@ -22,7 +22,6 @@ class VacationRequestActivity extends Model use HasFactory; protected $guarded = []; - protected $casts = [ "from" => VacationRequestState::class, "to" => VacationRequestState::class, diff --git a/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php b/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php new file mode 100644 index 0000000..3d0fb36 --- /dev/null +++ b/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php @@ -0,0 +1,76 @@ +type(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))->all()) + ->get(); + + /** @var VacationRequest $vacationRequest */ + foreach ($vacationRequests as $vacationRequest) { + if (!$this->shouldNotify($vacationRequest)) { + continue; + } + + if ($vacationRequest->state->equals(WaitingForTechnical::class)) { + $this->notifyTechnicalApprovers($vacationRequest); + } + + if ($vacationRequest->state->equals(WaitingForAdministrative::class)) { + $this->notifyAdminApprovers($vacationRequest); + } + } + } + + protected function shouldNotify(VacationRequest $vacationRequest): bool + { + $today = Carbon::today(); + $diff = $vacationRequest->updated_at->diffInDays($today); + + return $diff >= static::REMINDER_INTERVAL && ($diff % static::REMINDER_INTERVAL === 0); + } + + 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)); + } + } + + protected function notifyTechnicalApprovers(VacationRequest $vacationRequest): void + { + $users = User::query() + ->whereIn("role", [Role::TechnicalApprover, Role::Administrator]) + ->get(); + + foreach ($users as $user) { + $user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user)); + } + } +} diff --git a/app/Infrastructure/Http/Kernel.php b/app/Infrastructure/Http/Kernel.php index 5c6a238..5b1e42f 100644 --- a/app/Infrastructure/Http/Kernel.php +++ b/app/Infrastructure/Http/Kernel.php @@ -40,7 +40,6 @@ class Kernel extends HttpKernel TrimStrings::class, ConvertEmptyStringsToNull::class, ]; - protected $middlewareGroups = [ "web" => [ EncryptCookies::class, @@ -58,7 +57,6 @@ class Kernel extends HttpKernel SubstituteBindings::class, ], ]; - protected $routeMiddleware = [ "auth" => Authenticate::class, "auth.basic" => AuthenticateWithBasicAuth::class, diff --git a/tests/Unit/SendVacationRequestRemindersTest.php b/tests/Unit/SendVacationRequestRemindersTest.php new file mode 100644 index 0000000..a7ebc57 --- /dev/null +++ b/tests/Unit/SendVacationRequestRemindersTest.php @@ -0,0 +1,142 @@ +createCurrentYearPeriod(); + + Notification::fake(); + } + + public function testReminderIsSentIfItsBeenThreeDaysSinceTheUpdate(): void + { + $currentYearPeriod = YearPeriod::current(); + $now = Carbon::today(); + $this->travelTo($now); + + $user = User::factory()->create(); + $technicalApprover = User::factory() + ->technicalApprover() + ->create(); + + VacationRequest::factory([ + "type" => VacationType::Vacation->value, + "state" => WaitingForTechnical::class, + ]) + ->for($user) + ->for($currentYearPeriod) + ->create(); + + $this->travelTo($now->addDays(3)); + + $this->artisan(SendVacationRequestRemindersToApprovers::class); + + Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); + } + + public function testReminderIsSentIfItsBeenAnotherThreeDaysSinceTheUpdate(): void + { + $currentYearPeriod = YearPeriod::current(); + $now = Carbon::today(); + $this->travelTo($now); + + $user = User::factory()->create(); + $technicalApprover = User::factory() + ->technicalApprover() + ->create(); + + VacationRequest::factory([ + "type" => VacationType::Vacation->value, + "state" => WaitingForTechnical::class, + ]) + ->for($user) + ->for($currentYearPeriod) + ->create(); + + $this->travelTo($now->addDays(6)); + + $this->artisan(SendVacationRequestRemindersToApprovers::class); + + Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); + } + + public function testReminderIsNotSentIfItHasntBeenThreeDays(): void + { + $currentYearPeriod = YearPeriod::current(); + $now = Carbon::today(); + $this->travelTo($now); + + $user = User::factory()->create(); + $technicalApprover = User::factory() + ->technicalApprover() + ->create(); + + VacationRequest::factory([ + "type" => VacationType::Vacation->value, + "state" => WaitingForTechnical::class, + ]) + ->for($user) + ->for($currentYearPeriod) + ->create(); + + $this->travelTo($now->addDays(2)); + + $this->artisan(SendVacationRequestRemindersToApprovers::class); + + Notification::assertNotSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); + } + + public function testReminderIsSentToProperApprover(): void + { + $currentYearPeriod = YearPeriod::current(); + $now = Carbon::today(); + $this->travelTo($now); + + $user = User::factory()->create(); + $adminApprover = User::factory() + ->administrativeApprover() + ->create(); + $technicalApprover = User::factory() + ->technicalApprover() + ->create(); + + VacationRequest::factory([ + "type" => VacationType::Vacation->value, + "state" => WaitingForAdministrative::class, + ]) + ->for($user) + ->for($currentYearPeriod) + ->create(); + + $this->travelTo($now->addDays(3)); + + $this->artisan(SendVacationRequestRemindersToApprovers::class); + + Notification::assertSentTo([$adminApprover], VacationRequestWaitsForApprovalNotification::class); + Notification::assertNotSentTo([$technicalApprover, $user], VacationRequestWaitsForApprovalNotification::class); + } +} -- 2.52.0 From eb5dcf39c6f9e64594647512e14ec2ca42658d57 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Mon, 25 Apr 2022 15:49:33 +0200 Subject: [PATCH 02/15] #126 - fix workdays --- .../Actions/VacationRequest/CreateAction.php | 10 +++---- .../Rules/DoesNotExceedLimitRule.php | 8 +++--- .../Rules/MinimumOneVacationDayRule.php | 6 ++--- ...sCalculator.php => WorkDaysCalculator.php} | 5 ++-- ...endVacationRequestRemindersToApprovers.php | 14 +++++----- .../Api/CalculateVacationDaysController.php | 6 ++--- database/seeders/DatabaseSeeder.php | 4 +-- database/seeders/DemoSeeder.php | 8 +++--- .../Unit/SendVacationRequestRemindersTest.php | 26 ++++++++----------- 9 files changed, 42 insertions(+), 45 deletions(-) rename app/Domain/{VacationDaysCalculator.php => WorkDaysCalculator.php} (81%) diff --git a/app/Domain/Actions/VacationRequest/CreateAction.php b/app/Domain/Actions/VacationRequest/CreateAction.php index 8a823e3..43d9736 100644 --- a/app/Domain/Actions/VacationRequest/CreateAction.php +++ b/app/Domain/Actions/VacationRequest/CreateAction.php @@ -6,10 +6,10 @@ namespace Toby\Domain\Actions\VacationRequest; use Illuminate\Validation\ValidationException; use Toby\Domain\Notifications\VacationRequestCreatedNotification; -use Toby\Domain\VacationDaysCalculator; 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; @@ -19,7 +19,7 @@ class CreateAction protected VacationRequestStateManager $stateManager, protected VacationRequestValidator $vacationRequestValidator, protected VacationTypeConfigRetriever $configRetriever, - protected VacationDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $vacationDaysCalculator, protected WaitForTechApprovalAction $waitForTechApprovalAction, protected WaitForAdminApprovalAction $waitForAdminApprovalAction, protected ApproveAction $approveAction, @@ -52,11 +52,7 @@ class CreateAction $vacationRequest->save(); - $days = $this->vacationDaysCalculator->calculateDays( - $vacationRequest->yearPeriod, - $vacationRequest->from, - $vacationRequest->to, - ); + $days = $this->vacationDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); foreach ($days as $day) { $vacationRequest->vacations()->create([ diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index 3fd5429..e563ab6 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -7,9 +7,9 @@ namespace Toby\Domain\Validation\Rules; use Illuminate\Database\Eloquent\Builder; 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 $vacationDaysCalculator, ) {} 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->vacationDaysCalculator + ->calculateDays($vacationRequest->from, $vacationRequest->to) + ->count(); return $limit >= ($vacationDays + $estimatedDays); } diff --git a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php index ae9f58b..f32a6a3 100644 --- a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php +++ b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php @@ -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 $vacationDaysCalculator, ) {} public function check(VacationRequest $vacationRequest): bool { return $this->vacationDaysCalculator - ->calculateDays($vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to) + ->calculateDays($vacationRequest->from, $vacationRequest->to) ->isNotEmpty(); } diff --git a/app/Domain/VacationDaysCalculator.php b/app/Domain/WorkDaysCalculator.php similarity index 81% rename from app/Domain/VacationDaysCalculator.php rename to app/Domain/WorkDaysCalculator.php index 0e41a4a..3dd3c5f 100644 --- a/app/Domain/VacationDaysCalculator.php +++ b/app/Domain/WorkDaysCalculator.php @@ -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(); diff --git a/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php b/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php index 3d0fb36..99c0805 100644 --- a/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php +++ b/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php @@ -12,6 +12,7 @@ use Toby\Domain\Notifications\VacationRequestWaitsForApprovalNotification; use Toby\Domain\States\VacationRequest\WaitingForAdministrative; use Toby\Domain\States\VacationRequest\WaitingForTechnical; use Toby\Domain\VacationTypeConfigRetriever; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; @@ -22,7 +23,7 @@ class SendVacationRequestRemindersToApprovers extends Command protected $signature = "toby:send-vacation-request-reminders"; protected $description = "Sends vacation request reminders to approvers if they didn't approve"; - public function handle(VacationTypeConfigRetriever $configRetriever): void + public function handle(VacationTypeConfigRetriever $configRetriever, WorkDaysCalculator $daysCalculator): void { $vacationRequests = VacationRequest::query() ->type(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))->all()) @@ -30,7 +31,7 @@ class SendVacationRequestRemindersToApprovers extends Command /** @var VacationRequest $vacationRequest */ foreach ($vacationRequests as $vacationRequest) { - if (!$this->shouldNotify($vacationRequest)) { + if (!$this->shouldNotify($vacationRequest, $daysCalculator)) { continue; } @@ -44,12 +45,13 @@ class SendVacationRequestRemindersToApprovers extends Command } } - protected function shouldNotify(VacationRequest $vacationRequest): bool + protected function shouldNotify(VacationRequest $vacationRequest, WorkDaysCalculator $daysCalculator): bool { - $today = Carbon::today(); - $diff = $vacationRequest->updated_at->diffInDays($today); + $days = $daysCalculator + ->calculateDays($vacationRequest->updated_at->addDay(), Carbon::today()) + ->count(); - return $diff >= static::REMINDER_INTERVAL && ($diff % static::REMINDER_INTERVAL === 0); + return $days >= static::REMINDER_INTERVAL && ($days % static::REMINDER_INTERVAL === 0); } protected function notifyAdminApprovers(VacationRequest $vacationRequest): void diff --git a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php index c204be1..1c3f91b 100644 --- a/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php +++ b/app/Infrastructure/Http/Controllers/Api/CalculateVacationDaysController.php @@ -6,15 +6,15 @@ namespace Toby\Infrastructure\Http\Controllers\Api; use Illuminate\Http\JsonResponse; use Illuminate\Support\Carbon; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Infrastructure\Http\Controllers\Controller; use Toby\Infrastructure\Http\Requests\Api\CalculateVacationDaysRequest; class CalculateVacationDaysController extends Controller { - public function __invoke(CalculateVacationDaysRequest $request, VacationDaysCalculator $calculator): JsonResponse + public function __invoke(CalculateVacationDaysRequest $request, WorkDaysCalculator $calculator): JsonResponse { - $days = $calculator->calculateDays($request->yearPeriod(), $request->from(), $request->to()); + $days = $calculator->calculateDays($request->from(), $request->to()); return new JsonResponse($days->map(fn(Carbon $day) => $day->toDateString())->all()); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index f5e0e51..74e8b6a 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -7,7 +7,7 @@ namespace Database\Seeders; use Illuminate\Database\Seeder; use Illuminate\Support\Carbon; use Toby\Domain\PolishHolidaysRetriever; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\Key; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; @@ -70,7 +70,7 @@ class DatabaseSeeder extends Seeder "year_period_id" => $yearPeriods->random()->id, ]) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index 41470ba..d879612 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -18,7 +18,7 @@ use Toby\Domain\States\VacationRequest\Created; use Toby\Domain\States\VacationRequest\Rejected; use Toby\Domain\States\VacationRequest\WaitingForAdministrative; use Toby\Domain\States\VacationRequest\WaitingForTechnical; -use Toby\Domain\VacationDaysCalculator; +use Toby\Domain\WorkDaysCalculator; use Toby\Eloquent\Models\Key; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationLimit; @@ -164,7 +164,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, @@ -234,7 +234,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, @@ -291,7 +291,7 @@ class DemoSeeder extends Seeder ->for($user, "creator") ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { - $days = app(VacationDaysCalculator::class)->calculateDays( + $days = app(WorkDaysCalculator::class)->calculateDays( $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, diff --git a/tests/Unit/SendVacationRequestRemindersTest.php b/tests/Unit/SendVacationRequestRemindersTest.php index a7ebc57..6255744 100644 --- a/tests/Unit/SendVacationRequestRemindersTest.php +++ b/tests/Unit/SendVacationRequestRemindersTest.php @@ -32,11 +32,10 @@ class SendVacationRequestRemindersTest extends TestCase Notification::fake(); } - public function testReminderIsSentIfItsBeenThreeDaysSinceTheUpdate(): void + public function testReminderIsSentIfItsBeenThreeWorkDaysSinceTheUpdate(): void { $currentYearPeriod = YearPeriod::current(); - $now = Carbon::today(); - $this->travelTo($now); + $this->travelTo(Carbon::create(2022, 4, 20)); $user = User::factory()->create(); $technicalApprover = User::factory() @@ -51,18 +50,17 @@ class SendVacationRequestRemindersTest extends TestCase ->for($currentYearPeriod) ->create(); - $this->travelTo($now->addDays(3)); + $this->travelTo(Carbon::create(2022, 4, 25)); $this->artisan(SendVacationRequestRemindersToApprovers::class); Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); } - public function testReminderIsSentIfItsBeenAnotherThreeDaysSinceTheUpdate(): void + public function testReminderIsSentIfItsBeenAnotherThreeWorkDaysSinceTheUpdate(): void { $currentYearPeriod = YearPeriod::current(); - $now = Carbon::today(); - $this->travelTo($now); + $this->travelTo(Carbon::create(2022, 4, 20)); $user = User::factory()->create(); $technicalApprover = User::factory() @@ -77,18 +75,17 @@ class SendVacationRequestRemindersTest extends TestCase ->for($currentYearPeriod) ->create(); - $this->travelTo($now->addDays(6)); + $this->travelTo(Carbon::create(2022, 4, 28)); $this->artisan(SendVacationRequestRemindersToApprovers::class); Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); } - public function testReminderIsNotSentIfItHasntBeenThreeDays(): void + public function testReminderIsNotSentIfItHasntBeenThreeWorkDays(): void { $currentYearPeriod = YearPeriod::current(); - $now = Carbon::today(); - $this->travelTo($now); + $this->travelTo(Carbon::create(2022, 4, 20)); $user = User::factory()->create(); $technicalApprover = User::factory() @@ -103,7 +100,7 @@ class SendVacationRequestRemindersTest extends TestCase ->for($currentYearPeriod) ->create(); - $this->travelTo($now->addDays(2)); + $this->travelTo(Carbon::create(2022, 4, 24)); $this->artisan(SendVacationRequestRemindersToApprovers::class); @@ -113,8 +110,7 @@ class SendVacationRequestRemindersTest extends TestCase public function testReminderIsSentToProperApprover(): void { $currentYearPeriod = YearPeriod::current(); - $now = Carbon::today(); - $this->travelTo($now); + $this->travelTo(Carbon::create(2022, 4, 20)); $user = User::factory()->create(); $adminApprover = User::factory() @@ -132,7 +128,7 @@ class SendVacationRequestRemindersTest extends TestCase ->for($currentYearPeriod) ->create(); - $this->travelTo($now->addDays(3)); + $this->travelTo(Carbon::create(2022, 4, 25)); $this->artisan(SendVacationRequestRemindersToApprovers::class); -- 2.52.0 From 62a0a218356b48d3bfe385583520e5026908d6c0 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Tue, 26 Apr 2022 14:12:25 +0200 Subject: [PATCH 03/15] #126 - changes --- .../VacationRequestsSummaryNotification.php | 63 ++++++++ ...endVacationRequestRemindersToApprovers.php | 78 ---------- ...endVacationRequestSummariesToApprovers.php | 36 +++++ .../Unit/SendVacationRequestRemindersTest.php | 138 ------------------ 4 files changed, 99 insertions(+), 216 deletions(-) create mode 100644 app/Domain/Notifications/VacationRequestsSummaryNotification.php delete mode 100644 app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php create mode 100644 app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php delete mode 100644 tests/Unit/SendVacationRequestRemindersTest.php diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php new file mode 100644 index 0000000..e51a673 --- /dev/null +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -0,0 +1,63 @@ + "waiting_for_action", + ], + ); + + return $this->buildMailMessage($notifiable, $url); + } + + protected function buildMailMessage(User $user, string $url): MailMessage + { + $user = $user->profile->first_name; + + $message = (new MailMessage()) + ->greeting( + __("Hi :user!", [ + "user" => $user, + ]) + ) + ->line("Lista wniosków oczekujących na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") + ->subject("Wnioski oczekujące na akcje - stan na dzień {$this->day->toDisplayString()}"); + + foreach ($this->vacationRequests as $request) { + $message->line( + "Wniosek nr {$request->name} użytkownika {$request->user->profile->full_name} ({$request->from->toDisplayString()} - {$request->to->toDisplayString()})" + ); + } + + return $message + ->action("Przejdź do wniosków", $url); + } +} diff --git a/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php b/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php deleted file mode 100644 index 99c0805..0000000 --- a/app/Infrastructure/Console/Commands/SendVacationRequestRemindersToApprovers.php +++ /dev/null @@ -1,78 +0,0 @@ -type(VacationType::all()->filter(fn(VacationType $type) => $configRetriever->isVacation($type))->all()) - ->get(); - - /** @var VacationRequest $vacationRequest */ - foreach ($vacationRequests as $vacationRequest) { - if (!$this->shouldNotify($vacationRequest, $daysCalculator)) { - continue; - } - - if ($vacationRequest->state->equals(WaitingForTechnical::class)) { - $this->notifyTechnicalApprovers($vacationRequest); - } - - if ($vacationRequest->state->equals(WaitingForAdministrative::class)) { - $this->notifyAdminApprovers($vacationRequest); - } - } - } - - protected function shouldNotify(VacationRequest $vacationRequest, WorkDaysCalculator $daysCalculator): bool - { - $days = $daysCalculator - ->calculateDays($vacationRequest->updated_at->addDay(), Carbon::today()) - ->count(); - - return $days >= static::REMINDER_INTERVAL && ($days % static::REMINDER_INTERVAL === 0); - } - - 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)); - } - } - - protected function notifyTechnicalApprovers(VacationRequest $vacationRequest): void - { - $users = User::query() - ->whereIn("role", [Role::TechnicalApprover, Role::Administrator]) - ->get(); - - foreach ($users as $user) { - $user->notify(new VacationRequestWaitsForApprovalNotification($vacationRequest, $user)); - } - } -} diff --git a/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php b/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php new file mode 100644 index 0000000..9437021 --- /dev/null +++ b/app/Infrastructure/Console/Commands/SendVacationRequestSummariesToApprovers.php @@ -0,0 +1,36 @@ +whereIn("role", [Role::AdministrativeApprover, Role::TechnicalApprover, Role::Administrator]) + ->get(); + + foreach ($users as $user) { + $vacationRequests = VacationRequest::query() + ->states(VacationRequestStatesRetriever::waitingForUserActionStates($user)) + ->get(); + + if ($vacationRequests->isNotEmpty()) { + $user->notify(new VacationRequestsSummaryNotification(Carbon::today(), $vacationRequests)); + } + } + } +} diff --git a/tests/Unit/SendVacationRequestRemindersTest.php b/tests/Unit/SendVacationRequestRemindersTest.php deleted file mode 100644 index 6255744..0000000 --- a/tests/Unit/SendVacationRequestRemindersTest.php +++ /dev/null @@ -1,138 +0,0 @@ -createCurrentYearPeriod(); - - Notification::fake(); - } - - public function testReminderIsSentIfItsBeenThreeWorkDaysSinceTheUpdate(): void - { - $currentYearPeriod = YearPeriod::current(); - $this->travelTo(Carbon::create(2022, 4, 20)); - - $user = User::factory()->create(); - $technicalApprover = User::factory() - ->technicalApprover() - ->create(); - - VacationRequest::factory([ - "type" => VacationType::Vacation->value, - "state" => WaitingForTechnical::class, - ]) - ->for($user) - ->for($currentYearPeriod) - ->create(); - - $this->travelTo(Carbon::create(2022, 4, 25)); - - $this->artisan(SendVacationRequestRemindersToApprovers::class); - - Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); - } - - public function testReminderIsSentIfItsBeenAnotherThreeWorkDaysSinceTheUpdate(): void - { - $currentYearPeriod = YearPeriod::current(); - $this->travelTo(Carbon::create(2022, 4, 20)); - - $user = User::factory()->create(); - $technicalApprover = User::factory() - ->technicalApprover() - ->create(); - - VacationRequest::factory([ - "type" => VacationType::Vacation->value, - "state" => WaitingForTechnical::class, - ]) - ->for($user) - ->for($currentYearPeriod) - ->create(); - - $this->travelTo(Carbon::create(2022, 4, 28)); - - $this->artisan(SendVacationRequestRemindersToApprovers::class); - - Notification::assertSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); - } - - public function testReminderIsNotSentIfItHasntBeenThreeWorkDays(): void - { - $currentYearPeriod = YearPeriod::current(); - $this->travelTo(Carbon::create(2022, 4, 20)); - - $user = User::factory()->create(); - $technicalApprover = User::factory() - ->technicalApprover() - ->create(); - - VacationRequest::factory([ - "type" => VacationType::Vacation->value, - "state" => WaitingForTechnical::class, - ]) - ->for($user) - ->for($currentYearPeriod) - ->create(); - - $this->travelTo(Carbon::create(2022, 4, 24)); - - $this->artisan(SendVacationRequestRemindersToApprovers::class); - - Notification::assertNotSentTo([$technicalApprover], VacationRequestWaitsForApprovalNotification::class); - } - - public function testReminderIsSentToProperApprover(): void - { - $currentYearPeriod = YearPeriod::current(); - $this->travelTo(Carbon::create(2022, 4, 20)); - - $user = User::factory()->create(); - $adminApprover = User::factory() - ->administrativeApprover() - ->create(); - $technicalApprover = User::factory() - ->technicalApprover() - ->create(); - - VacationRequest::factory([ - "type" => VacationType::Vacation->value, - "state" => WaitingForAdministrative::class, - ]) - ->for($user) - ->for($currentYearPeriod) - ->create(); - - $this->travelTo(Carbon::create(2022, 4, 25)); - - $this->artisan(SendVacationRequestRemindersToApprovers::class); - - Notification::assertSentTo([$adminApprover], VacationRequestWaitsForApprovalNotification::class); - Notification::assertNotSentTo([$technicalApprover, $user], VacationRequestWaitsForApprovalNotification::class); - } -} -- 2.52.0 From 5363bbd47bb4d64e5d269c181336a338836a48bc Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Tue, 26 Apr 2022 14:20:41 +0200 Subject: [PATCH 04/15] #126 - cs fix --- .../Notifications/VacationRequestsSummaryNotification.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php index e51a673..7eb1de4 100644 --- a/app/Domain/Notifications/VacationRequestsSummaryNotification.php +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -18,8 +18,7 @@ class VacationRequestsSummaryNotification extends Notification public function __construct( protected Carbon $day, protected Collection $vacationRequests, - ) { - } + ) {} public function via(): array { @@ -46,14 +45,14 @@ class VacationRequestsSummaryNotification extends Notification ->greeting( __("Hi :user!", [ "user" => $user, - ]) + ]), ) ->line("Lista wniosków oczekujących na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") ->subject("Wnioski oczekujące na akcje - stan na dzień {$this->day->toDisplayString()}"); foreach ($this->vacationRequests as $request) { $message->line( - "Wniosek nr {$request->name} użytkownika {$request->user->profile->full_name} ({$request->from->toDisplayString()} - {$request->to->toDisplayString()})" + "Wniosek nr {$request->name} użytkownika {$request->user->profile->full_name} ({$request->from->toDisplayString()} - {$request->to->toDisplayString()})", ); } -- 2.52.0 From c2fcbfd875de370441e6015a8c5d85d0b47d77e2 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 09:30:01 +0200 Subject: [PATCH 05/15] #5 - bump codestyle --- composer.lock | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/composer.lock b/composer.lock index 8dd0196..bfe8055 100644 --- a/composer.lock +++ b/composer.lock @@ -7529,21 +7529,21 @@ "packages-dev": [ { "name": "blumilksoftware/codestyle", - "version": "v1.0.1", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/blumilksoftware/codestyle.git", - "reference": "e86ebcd5175bc435d9c8d4f83bf201a6f5a9fc60" + "reference": "3f2248859562afe7d7b2b16aa25e83dc73236fcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/blumilksoftware/codestyle/zipball/e86ebcd5175bc435d9c8d4f83bf201a6f5a9fc60", - "reference": "e86ebcd5175bc435d9c8d4f83bf201a6f5a9fc60", + "url": "https://api.github.com/repos/blumilksoftware/codestyle/zipball/3f2248859562afe7d7b2b16aa25e83dc73236fcc", + "reference": "3f2248859562afe7d7b2b16aa25e83dc73236fcc", "shasum": "" }, "require": { "friendsofphp/php-cs-fixer": "^3.8.0", - "kubawerlos/php-cs-fixer-custom-fixers": "^3.7", + "kubawerlos/php-cs-fixer-custom-fixers": "^3.10.1", "php": "^8.0" }, "require-dev": { @@ -7551,6 +7551,9 @@ "phpunit/phpunit": "^9.5", "symfony/console": "^6.0" }, + "bin": [ + "bin/codestyle" + ], "type": "library", "autoload": { "psr-4": { @@ -7570,9 +7573,9 @@ "description": "Blumilk codestyle configurator", "support": { "issues": "https://github.com/blumilksoftware/codestyle/issues", - "source": "https://github.com/blumilksoftware/codestyle/tree/v1.0.1" + "source": "https://github.com/blumilksoftware/codestyle/tree/v1.1.0" }, - "time": "2022-04-04T06:25:21+00:00" + "time": "2022-04-25T06:04:51+00:00" }, { "name": "composer/pcre", @@ -10931,5 +10934,5 @@ "ext-redis": "*" }, "platform-dev": [], - "plugin-api-version": "2.1.0" + "plugin-api-version": "2.2.0" } -- 2.52.0 From 29c06d625b914583e24bdebc60d15d3da395b12c Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 11:49:01 +0200 Subject: [PATCH 06/15] #126 - fix --- .../KeyHasBeenGivenNotification.php | 14 ++--- .../KeyHasBeenTakenNotification.php | 14 ++--- .../VacationRequestCreatedNotification.php | 37 +++++++------ ...cationRequestStatusChangedNotification.php | 9 ++-- ...ionRequestWaitsForApprovalNotification.php | 9 ++-- .../VacationRequestsSummaryNotification.php | 11 +++- .../Slack/Channels/SlackApiChannel.php | 7 +-- .../Slack/Elements/SlackMessage.php | 53 +++++++++++++++++++ .../Elements/VacationRequestsAttachment.php | 33 ++++++++++++ 9 files changed, 145 insertions(+), 42 deletions(-) create mode 100644 app/Infrastructure/Slack/Elements/SlackMessage.php create mode 100644 app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php diff --git a/app/Domain/Notifications/KeyHasBeenGivenNotification.php b/app/Domain/Notifications/KeyHasBeenGivenNotification.php index 977f5bf..2759396 100644 --- a/app/Domain/Notifications/KeyHasBeenGivenNotification.php +++ b/app/Domain/Notifications/KeyHasBeenGivenNotification.php @@ -7,6 +7,7 @@ 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 { @@ -22,13 +23,14 @@ class KeyHasBeenGivenNotification extends Notification return [Channels::SLACK]; } - public function toSlack(Notifiable $notifiable): string + public function toSlack(Notifiable $notifiable): SlackMessage { - return __(":sender gives key no :key to :recipient", [ - "sender" => $this->getName($this->sender), - "recipient" => $this->getName($this->recipient), - "key" => $notifiable->id, - ]); + 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 diff --git a/app/Domain/Notifications/KeyHasBeenTakenNotification.php b/app/Domain/Notifications/KeyHasBeenTakenNotification.php index 71e8b7b..435fc02 100644 --- a/app/Domain/Notifications/KeyHasBeenTakenNotification.php +++ b/app/Domain/Notifications/KeyHasBeenTakenNotification.php @@ -7,6 +7,7 @@ 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 { @@ -22,13 +23,14 @@ class KeyHasBeenTakenNotification extends Notification return [Channels::SLACK]; } - public function toSlack(Notifiable $notifiable): string + public function toSlack(Notifiable $notifiable): SlackMessage { - return __(":recipient takes key no :key from :sender", [ - "recipient" => $this->getName($this->recipient), - "sender" => $this->getName($this->sender), - "key" => $notifiable->id, - ]); + 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 diff --git a/app/Domain/Notifications/VacationRequestCreatedNotification.php b/app/Domain/Notifications/VacationRequestCreatedNotification.php index 9a352c6..187ee45 100644 --- a/app/Domain/Notifications/VacationRequestCreatedNotification.php +++ b/app/Domain/Notifications/VacationRequestCreatedNotification.php @@ -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 { @@ -23,14 +24,12 @@ class VacationRequestCreatedNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** @@ -56,19 +55,25 @@ class VacationRequestCreatedNotification extends Notification $days = $this->vacationRequest->vacations()->count(); return (new MailMessage()) - ->greeting(__("Hi :user!", [ - "user" => $user, - ])) + ->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, - ])) + ->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); } diff --git a/app/Domain/Notifications/VacationRequestStatusChangedNotification.php b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php index 746e3a0..cacd1e4 100644 --- a/app/Domain/Notifications/VacationRequestStatusChangedNotification.php +++ b/app/Domain/Notifications/VacationRequestStatusChangedNotification.php @@ -10,6 +10,7 @@ use Illuminate\Notifications\Notification; use InvalidArgumentException; use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\VacationRequest; +use Toby\Infrastructure\Slack\Elements\SlackMessage; class VacationRequestStatusChangedNotification extends Notification { @@ -25,14 +26,12 @@ class VacationRequestStatusChangedNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** diff --git a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php index 63ea770..b4033ad 100644 --- a/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php +++ b/app/Domain/Notifications/VacationRequestWaitsForApprovalNotification.php @@ -11,6 +11,7 @@ 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 VacationRequestWaitsForApprovalNotification extends Notification { @@ -26,14 +27,12 @@ class VacationRequestWaitsForApprovalNotification extends Notification return [Channels::MAIL, Channels::SLACK]; } - public function toSlack(): string + public function toSlack(): SlackMessage { $url = route("vacation.requests.show", ["vacationRequest" => $this->vacationRequest->id]); - return implode("\n", [ - $this->buildDescription(), - "<${url}|Zobacz szczegóły>", - ]); + return (new SlackMessage()) + ->text("{$this->buildDescription()}\n <${url}|Zobacz szczegóły>"); } /** diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php index 7eb1de4..a56d12a 100644 --- a/app/Domain/Notifications/VacationRequestsSummaryNotification.php +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -10,6 +10,8 @@ use Illuminate\Notifications\Notification; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Toby\Eloquent\Models\User; +use Toby\Infrastructure\Slack\Elements\SlackMessage; +use Toby\Infrastructure\Slack\Elements\VacationRequestsAttachment; class VacationRequestsSummaryNotification extends Notification { @@ -22,7 +24,14 @@ class VacationRequestsSummaryNotification extends Notification public function via(): array { - return ["mail"]; + return [Channels::MAIL, Channels::SLACK]; + } + + public function toSlack(): SlackMessage + { + return (new SlackMessage()) + ->text("Lista wniosków oczekujących na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") + ->withAttachment(new VacationRequestsAttachment($this->vacationRequests)); } public function toMail($notifiable): MailMessage diff --git a/app/Infrastructure/Slack/Channels/SlackApiChannel.php b/app/Infrastructure/Slack/Channels/SlackApiChannel.php index d790555..b8a9e67 100644 --- a/app/Infrastructure/Slack/Channels/SlackApiChannel.php +++ b/app/Infrastructure/Slack/Channels/SlackApiChannel.php @@ -17,11 +17,12 @@ class SlackApiChannel $url = "{$baseUrl}/chat.postMessage"; $channel = $notifiable->routeNotificationFor("slack", $notification); + $message = $notification->toSlack($notifiable); + return Http::withToken($this->getClientToken()) - ->post($url, [ + ->post($url, array_merge($message->getPayload(), [ "channel" => $channel, - "text" => $notification->toSlack($notifiable), - ]); + ])); } protected function getClientToken(): string diff --git a/app/Infrastructure/Slack/Elements/SlackMessage.php b/app/Infrastructure/Slack/Elements/SlackMessage.php new file mode 100644 index 0000000..05d8e1b --- /dev/null +++ b/app/Infrastructure/Slack/Elements/SlackMessage.php @@ -0,0 +1,53 @@ +attachments = new Collection(); + } + + public function text(string $text): static + { + $this->text = $text; + + return $this; + } + + public function withAttachment(Attachment $attachment): static + { + $this->attachments->push($attachment); + + return $this; + } + + public function withAttachments(Collection $attachments): static + { + foreach ($attachments as $attachment) { + $this->withAttachment($attachment); + } + + return $this; + } + + public function getPayload(): array + { + return [ + "text" => $this->text, + "link_names" => true, + "unfurl_links" => true, + "unfurl_media" => true, + "mrkdwn" => true, + "attachments" => $this->attachments->toArray(), + ]; + } +} diff --git a/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php b/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php new file mode 100644 index 0000000..933b8fb --- /dev/null +++ b/app/Infrastructure/Slack/Elements/VacationRequestsAttachment.php @@ -0,0 +1,33 @@ +setColor("#527aba") + ->setItems($this->mapVacationRequests($vacationRequests)); + } + + protected function mapVacationRequests(Collection $vacationRequests): Collection + { + return $vacationRequests->map(function (VacationRequest $request): string { + $url = route("vacation.requests.show", ["vacationRequest" => $request->id]); + + $date = $request->from->equalTo($request->to) + ? "{$request->from->toDisplayString()}" + : "{$request->from->toDisplayString()} - {$request->to->toDisplayString()}"; + + return "<{$url}|Wniosek nr {$request->name}> użytkownika {$request->user->profile->full_name} ({$date})"; + }); + } +} -- 2.52.0 From 1f01d334ca0bb3e71824a4515481833a3f52296e Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 12:00:21 +0200 Subject: [PATCH 07/15] #126 - fix --- routes/api.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/api.php b/routes/api.php index 3220d19..7fa9ef1 100644 --- a/routes/api.php +++ b/routes/api.php @@ -7,9 +7,9 @@ use Toby\Infrastructure\Http\Controllers\Api\CalculateUserUnavailableDaysControl use Toby\Infrastructure\Http\Controllers\Api\CalculateUserVacationStatsController; use Toby\Infrastructure\Http\Controllers\Api\CalculateVacationDaysController; use Toby\Infrastructure\Http\Controllers\Api\GetAvailableVacationTypesController; -use Toby\Infrastructure\Slack\Controller as SlackController; +use Toby\Infrastructure\Slack\Controller as SlackCommandController; -Route::post("slack", [SlackController::class, "getResponse"]); +Route::post("slack", [SlackCommandController::class, "getResponse"]); Route::middleware("auth:sanctum")->group(function (): void { Route::post("vacation/calculate-days", CalculateVacationDaysController::class); -- 2.52.0 From 0b154af1d5e99446121f9a2824b41dc737f5667c Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 12:12:36 +0200 Subject: [PATCH 08/15] #126 - fix --- app/Domain/Actions/VacationRequest/CreateAction.php | 4 ++-- app/Domain/Validation/Rules/DoesNotExceedLimitRule.php | 4 ++-- app/Domain/Validation/Rules/MinimumOneVacationDayRule.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Domain/Actions/VacationRequest/CreateAction.php b/app/Domain/Actions/VacationRequest/CreateAction.php index 43d9736..b569954 100644 --- a/app/Domain/Actions/VacationRequest/CreateAction.php +++ b/app/Domain/Actions/VacationRequest/CreateAction.php @@ -19,7 +19,7 @@ class CreateAction protected VacationRequestStateManager $stateManager, protected VacationRequestValidator $vacationRequestValidator, protected VacationTypeConfigRetriever $configRetriever, - protected WorkDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, protected WaitForTechApprovalAction $waitForTechApprovalAction, protected WaitForAdminApprovalAction $waitForAdminApprovalAction, protected ApproveAction $approveAction, @@ -52,7 +52,7 @@ class CreateAction $vacationRequest->save(); - $days = $this->vacationDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); + $days = $this->workDaysCalculator->calculateDays($vacationRequest->from, $vacationRequest->to); foreach ($days as $day) { $vacationRequest->vacations()->create([ diff --git a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php index 780b749..c0ec8df 100644 --- a/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php +++ b/app/Domain/Validation/Rules/DoesNotExceedLimitRule.php @@ -18,7 +18,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule { public function __construct( protected VacationTypeConfigRetriever $configRetriever, - protected WorkDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, ) {} public function check(VacationRequest $vacationRequest): bool @@ -29,7 +29,7 @@ class DoesNotExceedLimitRule implements VacationRequestRule $limit = $this->getUserVacationLimit($vacationRequest->user, $vacationRequest->yearPeriod); $vacationDays = $this->getVacationDaysWithLimit($vacationRequest->user, $vacationRequest->yearPeriod); - $estimatedDays = $this->vacationDaysCalculator + $estimatedDays = $this->workDaysCalculator ->calculateDays($vacationRequest->from, $vacationRequest->to) ->count(); diff --git a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php index f32a6a3..5e78a75 100644 --- a/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php +++ b/app/Domain/Validation/Rules/MinimumOneVacationDayRule.php @@ -10,12 +10,12 @@ use Toby\Eloquent\Models\VacationRequest; class MinimumOneVacationDayRule implements VacationRequestRule { public function __construct( - protected WorkDaysCalculator $vacationDaysCalculator, + protected WorkDaysCalculator $workDaysCalculator, ) {} public function check(VacationRequest $vacationRequest): bool { - return $this->vacationDaysCalculator + return $this->workDaysCalculator ->calculateDays($vacationRequest->from, $vacationRequest->to) ->isNotEmpty(); } -- 2.52.0 From 7b3235fea2962a267496c64d9fd67eb5e7eb156a Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 13:19:32 +0200 Subject: [PATCH 09/15] #126 - fix --- resources/js/Pages/VacationRequest/Show.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/Pages/VacationRequest/Show.vue b/resources/js/Pages/VacationRequest/Show.vue index ad58a1e..a606539 100644 --- a/resources/js/Pages/VacationRequest/Show.vue +++ b/resources/js/Pages/VacationRequest/Show.vue @@ -77,7 +77,7 @@
{{ request.comment }}
-- 2.52.0 From 62cc3c3cd40e864b7d0b36df32bf9c5ba76010e9 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 13:36:22 +0200 Subject: [PATCH 10/15] #126 - tests --- .../VacationRequestsSummaryNotification.php | 2 +- .../Unit/SendVacationRequestSummariesTest.php | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/SendVacationRequestSummariesTest.php diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php index a56d12a..e5132b7 100644 --- a/app/Domain/Notifications/VacationRequestsSummaryNotification.php +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -30,7 +30,7 @@ class VacationRequestsSummaryNotification extends Notification public function toSlack(): SlackMessage { return (new SlackMessage()) - ->text("Lista wniosków oczekujących na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") + ->text("Wnioski oczekujące na Twoją akcję - stan na dzień {$this->day->toDisplayString()}:") ->withAttachment(new VacationRequestsAttachment($this->vacationRequests)); } diff --git a/tests/Unit/SendVacationRequestSummariesTest.php b/tests/Unit/SendVacationRequestSummariesTest.php new file mode 100644 index 0000000..e1dabb5 --- /dev/null +++ b/tests/Unit/SendVacationRequestSummariesTest.php @@ -0,0 +1,128 @@ +createCurrentYearPeriod(); + } + + public function testSummariesAreSentOnlyToProperApprovers(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $administrativeApprover = User::factory([ + "role" => Role::AdministrativeApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => WaitingForTechnical::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + } + + public function testSummariesAreSentOnlyIfVacationRequestWaitingForActionExists(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => WaitingForTechnical::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + } + + public function testSummariesAreNotSendIfTherAreNoWaitingForActionVacationRequests(): void + { + $currentYearPeriod = YearPeriod::current(); + + $user = User::factory([ + "role" => Role::Employee, + ])->create(); + $technicalApprover = User::factory([ + "role" => Role::TechnicalApprover, + ])->create(); + $admin = User::factory([ + "role" => Role::Administrator, + ])->create(); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Approved::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Cancelled::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Rejected::class]); + + VacationRequest::factory() + ->for($user) + ->for($currentYearPeriod) + ->create(["state" => Created::class]); + + $this->artisan(SendVacationRequestSummariesToApprovers::class) + ->execute(); + + Notification::assertNotSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + } +} -- 2.52.0 From 0957c2ae506c50e2cc6a7ff9fedc7d4d6c5fb6cd Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 13:55:26 +0200 Subject: [PATCH 11/15] #126 - fix --- tests/Unit/SendVacationRequestSummariesTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Unit/SendVacationRequestSummariesTest.php b/tests/Unit/SendVacationRequestSummariesTest.php index e1dabb5..ea1eb0e 100644 --- a/tests/Unit/SendVacationRequestSummariesTest.php +++ b/tests/Unit/SendVacationRequestSummariesTest.php @@ -86,7 +86,7 @@ class SendVacationRequestSummariesTest extends TestCase Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); } - public function testSummariesAreNotSendIfTherAreNoWaitingForActionVacationRequests(): void + public function testSummariesAreNotSentIfThereAreNoWaitingForActionVacationRequests(): void { $currentYearPeriod = YearPeriod::current(); -- 2.52.0 From 4d23469559c9ffb5c1cbb218b6f7f5df01b47ec4 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 14:29:08 +0200 Subject: [PATCH 12/15] #126 - fix --- .env.example | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.env.example b/.env.example index fe3a597..0689964 100644 --- a/.env.example +++ b/.env.example @@ -60,3 +60,8 @@ GOOGLE_CLIENT_SECRET= GOOGLE_REDIRECT=http://localhost/login/google/end GOOGLE_CALENDAR_ID= LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE= + +SLACK_URL=https://slack.com/api +SLACK_CLIENT_TOKEN= +SLACK_SIGNING_SECRET= +SLACK_DEFAULT_CHANNEL="#general" -- 2.52.0 From ceb939a65ef7b6d98a0bc8e04695ade50a12ffad Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 14:35:29 +0200 Subject: [PATCH 13/15] #126 - fix seeders --- database/seeders/DatabaseSeeder.php | 1 - database/seeders/DemoSeeder.php | 3 --- 2 files changed, 4 deletions(-) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 74e8b6a..ea21c71 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -71,7 +71,6 @@ class DatabaseSeeder extends Seeder ]) ->afterCreating(function (VacationRequest $vacationRequest): void { $days = app(WorkDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, ); diff --git a/database/seeders/DemoSeeder.php b/database/seeders/DemoSeeder.php index d879612..5d321ab 100644 --- a/database/seeders/DemoSeeder.php +++ b/database/seeders/DemoSeeder.php @@ -165,7 +165,6 @@ class DemoSeeder extends Seeder ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { $days = app(WorkDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, ); @@ -235,7 +234,6 @@ class DemoSeeder extends Seeder ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { $days = app(WorkDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, ); @@ -292,7 +290,6 @@ class DemoSeeder extends Seeder ->for($currentYearPeriod) ->afterCreating(function (VacationRequest $vacationRequest): void { $days = app(WorkDaysCalculator::class)->calculateDays( - $vacationRequest->yearPeriod, $vacationRequest->from, $vacationRequest->to, ); -- 2.52.0 From c5a92ca7a14f280bf0abcd5ee6ec7b4ff69fb766 Mon Sep 17 00:00:00 2001 From: Adrian Hopek Date: Wed, 27 Apr 2022 15:06:29 +0200 Subject: [PATCH 14/15] #126 - fix --- .../Notifications/VacationRequestsSummaryNotification.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/Domain/Notifications/VacationRequestsSummaryNotification.php b/app/Domain/Notifications/VacationRequestsSummaryNotification.php index e5132b7..1865b1b 100644 --- a/app/Domain/Notifications/VacationRequestsSummaryNotification.php +++ b/app/Domain/Notifications/VacationRequestsSummaryNotification.php @@ -9,7 +9,6 @@ use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notification; use Illuminate\Support\Carbon; use Illuminate\Support\Collection; -use Toby\Eloquent\Models\User; use Toby\Infrastructure\Slack\Elements\SlackMessage; use Toby\Infrastructure\Slack\Elements\VacationRequestsAttachment; @@ -34,7 +33,7 @@ class VacationRequestsSummaryNotification extends Notification ->withAttachment(new VacationRequestsAttachment($this->vacationRequests)); } - public function toMail($notifiable): MailMessage + public function toMail(Notifiable $notifiable): MailMessage { $url = route( "vacation.requests.indexForApprovers", @@ -46,9 +45,9 @@ class VacationRequestsSummaryNotification extends Notification return $this->buildMailMessage($notifiable, $url); } - protected function buildMailMessage(User $user, string $url): MailMessage + protected function buildMailMessage(Notifiable $notifiable, string $url): MailMessage { - $user = $user->profile->first_name; + $user = $notifiable->profile->first_name; $message = (new MailMessage()) ->greeting( -- 2.52.0 From fc206a4c14021a94563c168cb4aa5fa5155a73bf Mon Sep 17 00:00:00 2001 From: EwelinaLasowy Date: Wed, 27 Apr 2022 15:11:48 +0200 Subject: [PATCH 15/15] #126 - tests --- tests/Unit/SendVacationRequestSummariesTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Unit/SendVacationRequestSummariesTest.php b/tests/Unit/SendVacationRequestSummariesTest.php index ea1eb0e..97370bd 100644 --- a/tests/Unit/SendVacationRequestSummariesTest.php +++ b/tests/Unit/SendVacationRequestSummariesTest.php @@ -59,6 +59,7 @@ class SendVacationRequestSummariesTest extends TestCase ->execute(); Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + Notification::assertNotSentTo([$user, $administrativeApprover], VacationRequestsSummaryNotification::class); } public function testSummariesAreSentOnlyIfVacationRequestWaitingForActionExists(): void @@ -84,6 +85,7 @@ class SendVacationRequestSummariesTest extends TestCase ->execute(); Notification::assertSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + Notification::assertNotSentTo([$user], VacationRequestsSummaryNotification::class); } public function testSummariesAreNotSentIfThereAreNoWaitingForActionVacationRequests(): void @@ -123,6 +125,6 @@ class SendVacationRequestSummariesTest extends TestCase $this->artisan(SendVacationRequestSummariesToApprovers::class) ->execute(); - Notification::assertNotSentTo([$technicalApprover, $admin], VacationRequestsSummaryNotification::class); + Notification::assertNotSentTo([$user, $technicalApprover, $admin], VacationRequestsSummaryNotification::class); } } -- 2.52.0