This commit is contained in:
EwelinaLasowy 2022-02-11 13:02:35 +01:00
parent d026d41715
commit a0b64e6cb3
9 changed files with 500 additions and 3 deletions

View File

@ -1,4 +1,4 @@
APP_NAME="Toby HR applicaiton"
APP_NAME="Toby HR application"
APP_ENV=local
APP_KEY=
APP_DEBUG=true

View File

@ -0,0 +1,67 @@
<?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 VacationRequestNotification extends Notification
{
use Queueable;
protected User $user;
protected VacationRequest $vacationRequest;
public function __construct(User $user, VacationRequest $vacationRequest)
{
$this->user = $user;
$this->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
{
$title = $this->vacationRequest->name;
$state = $this->vacationRequest->state->label();
$user = $this->user->getFullNameAttribute();
return (new MailMessage())
->greeting(__("Hi :user!", [
"user" => $user,
]))
->subject(__("Vacation request :title", [
"title" => $title,
]))
->line(__("The vacation request :title has changed state to :state.", [
"title" => $title,
"state" => $state,
]))
->action(__("Show vacation request"), $url);
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Toby\Domain;
use Illuminate\Support\Collection;
use Toby\Domain\Enums\Role;
use Toby\Domain\Notifications\VacationRequestNotification;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
class VacationRequestNotificationSender
{
public function sendVacationRequestNotification(VacationRequest $vacationRequest): void
{
foreach ($this->getUsersForNotifications() as $user) {
$user->notify(new VacationRequestNotification($user, $vacationRequest));
}
$vacationRequest->user->notify(new VacationRequestNotification($vacationRequest->user, $vacationRequest));
}
protected function getUsersForNotifications(): Collection
{
return User::query()
->where("role", Role::TECHNICAL_APPROVER)
->orWhere("role", Role::ADMINISTRATIVE_APPROVER)
->get();
}
}

View File

@ -8,6 +8,7 @@ use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Events\Dispatcher;
use Toby\Domain\Enums\VacationRequestState;
use Toby\Domain\Events\VacationRequestStateChanged;
use Toby\Domain\VacationRequestNotificationSender;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
@ -16,6 +17,7 @@ class VacationRequestObserver
public function __construct(
protected Auth $auth,
protected Dispatcher $dispatcher,
protected VacationRequestNotificationSender $vacationRequestNotificationSender,
) {
}
@ -39,6 +41,13 @@ class VacationRequestObserver
}
}
public function updated(VacationRequest $vacationRequest): void
{
if ($vacationRequest->state !== VacationRequestState::CREATED) {
$this->vacationRequestNotificationSender->sendVacationRequestNotification($vacationRequest);
}
}
protected function fireStateChangedEvent(
VacationRequest $vacationRequest,
?VacationRequestState $from,

View File

@ -35,7 +35,7 @@ return [
"name" => env("MAIL_FROM_NAME", "Example"),
],
"markdown" => [
"theme" => "default",
"theme" => "mail",
"paths" => [
resource_path("views/vendor/mail"),
],

View File

@ -30,5 +30,12 @@
"You have approved vacation request in this range.": "Masz zaakceptowany wniosek urlopowy w tym zakresie dat.",
"You have exceeded your vacation limit.": "Przekroczyłeś/aś limit urlopu.",
"Vacation needs minimum one day.": "Urlop musi być co najmniej na jeden dzień.",
"The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku."
"The vacation request cannot be created at the turn of the year.": "Wniosek urlopowy nie może zostać złożony na przełomie roku.",
"Hi :user!": "Cześć :user!",
"The vacation request :title has changed state to :state.": "Wniosek urlopowy :title zmienił status na :state.",
"Vacation request :title": "Wniosek urlopowy :title",
"Regards": "Z poważaniem",
"If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\ninto your web browser:": "Jeżeli masz problemy z kliknięciem przycisku \":actionText\", skopiuj i wklej poniższy adres w pasek przeglądarki:",
"All rights reserved.": "Wszelkie prawa zastrzeżone",
"Show vacation request": "Pokaż wniosek"
}

View File

@ -0,0 +1,286 @@
/* Base */
body, body *:not(html):not(style):not(br):not(tr):not(code) {
font-family: Avenir, Helvetica, sans-serif;
box-sizing: border-box;
}
body {
background-color: #f5f8fa;
color: #74787e;
height: 100%;
hyphens: auto;
line-height: 1.4;
margin: 0;
-moz-hyphens: auto;
-ms-word-break: break-all;
width: 100% !important;
-webkit-hyphens: auto;
-webkit-text-size-adjust: none;
word-break: break-all;
word-break: break-word;
}
p,
ul,
ol,
blockquote {
line-height: 1.4;
text-align: left;
}
a {
color: #3c5f97;
}
a img {
border: none;
}
/* Typography */
h1 {
color: #2F3133;
font-size: 19px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h2 {
color: #2F3133;
font-size: 16px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
h3 {
color: #2F3133;
font-size: 14px;
font-weight: bold;
margin-top: 0;
text-align: left;
}
p {
color: #74787e;
font-size: 16px;
line-height: 1.5em;
margin-top: 0;
text-align: left;
}
p.sub {
font-size: 12px;
}
img {
max-width: 100%;
}
/* Layout */
.wrapper {
background-color: #f5f8fa;
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.content {
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
/* Header */
.header {
padding: 25px 0;
text-align: center;
}
.header a {
color: #3c5f97;
font-size: 19px;
font-weight: bold;
text-decoration: none;
text-shadow: 0 1px 0 #ffffff;
}
/* Body */
.body {
background-color: #ffffff;
border-bottom: 1px solid #edeff2;
border-top: 1px solid #edeff2;
margin: 0;
padding: 0;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.inner-body {
background-color: #ffffff;
margin: 0 auto;
padding: 0;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
}
/* Subcopy */
.subcopy {
border-top: 1px solid #edeff2;
margin-top: 25px;
padding-top: 25px;
}
.subcopy p {
font-size: 12px;
}
/* Footer */
.footer {
margin: 0 auto;
padding: 0;
text-align: center;
width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 570px;
}
.footer p {
color: #aeaeae;
font-size: 12px;
text-align: center;
}
/* Tables */
.table table {
margin: 30px auto;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.table th {
color: #74787e;
border-bottom: 1px solid #edeff2;
padding-bottom: 8px;
}
.table td {
color: #74787e;
font-size: 15px;
line-height: 18px;
padding: 10px 0;
}
.content-cell {
padding: 35px;
}
/* Buttons */
.action {
margin: 30px auto;
padding: 0;
text-align: center;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.button {
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
color: #ffffff;
display: inline-block;
text-decoration: none;
-webkit-text-size-adjust: none;
}
.button-blue, .button-primary {
background-color: #3c5f97;
border-top: 10px solid #3c5f97;
border-right: 18px solid #3c5f97;
border-bottom: 10px solid #3c5f97;
border-left: 18px solid #3c5f97;
}
.button-green, .button-success {
background-color: #22c55e;
border-top: 10px solid #22c55e;
border-right: 18px solid #22c55e;
border-bottom: 10px solid #22c55e;
border-left: 18px solid #22c55e;
}
.button-red, .button-error {
background-color: #ef4444;
border-top: 10px solid #ef4444;
border-right: 18px solid #ef4444;
border-bottom: 10px solid #ef4444;
border-left: 18px solid #ef4444;
}
/* Panels */
.panel {
margin: 0 0 21px;
}
.panel-content {
background-color: #edeff2;
padding: 16px;
}
.panel-item {
padding: 0;
}
.panel-item p:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
}
/* Promotions */
.promotion {
background-color: #FFFFFF;
border: 2px dashed #9BA2AB;
margin: 0;
margin-bottom: 25px;
margin-top: 25px;
padding: 24px;
width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
-premailer-width: 100%;
}
.promotion h1 {
text-align: center;
}
.promotion p {
font-size: 15px;
text-align: center;
}

View File

@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Tests\Unit;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Carbon;
use Tests\TestCase;
use Tests\Traits\InteractsWithYearPeriods;
use Toby\Domain\Enums\Role;
use Toby\Domain\Enums\VacationRequestState;
use Toby\Domain\Enums\VacationType;
use Toby\Domain\Notifications\VacationRequestNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod;
class VacationRequestNotificationTest extends TestCase
{
use DatabaseMigrations;
use InteractsWithYearPeriods;
protected VacationRequestStateManager $stateManager;
protected function setUp(): void
{
parent::setUp();
$this->stateManager = $this->app->make(VacationRequestStateManager::class);
$this->createCurrentYearPeriod();
}
public function testAfterChangingVacationRequestStateNotificationAreSentToUsers() :void
{
Notification::fake();
$user = User::factory(["role" => Role::EMPLOYEE])->createQuietly();
$technicalApprover = User::factory(["role" => Role::TECHNICAL_APPROVER])->createQuietly();
$administrativeApprover = User::factory(["role" => Role::ADMINISTRATIVE_APPROVER])->createQuietly();
$currentYearPeriod = YearPeriod::current();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([
"type" => VacationType::VACATION->value,
"state" => VacationRequestState::CREATED,
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
"comment" => "Comment for the vacation request.",
])
->for($user)
->for($currentYearPeriod)
->create();
$this->stateManager->waitForTechnical($vacationRequest);
Notification::assertSentTo([$user, $technicalApprover, $administrativeApprover],VacationRequestNotification::class);
}
public function testAfterChangingVacationRequestStateNotificationIsNotSentToAnotherEmployee(): void {
Notification::fake();
$user = User::factory(["role" => Role::EMPLOYEE])->createQuietly();
$anotherUser = User::factory(["role" => Role::EMPLOYEE])->createQuietly();
$technicalApprover = User::factory(["role" => Role::TECHNICAL_APPROVER])->createQuietly();
$administrativeApprover = User::factory(["role" => Role::ADMINISTRATIVE_APPROVER])->createQuietly();
$currentYearPeriod = YearPeriod::current();
/** @var VacationRequest $vacationRequest */
$vacationRequest = VacationRequest::factory([
"type" => VacationType::VACATION->value,
"state" => VacationRequestState::CREATED,
"from" => Carbon::create($currentYearPeriod->year, 2, 1)->toDateString(),
"to" => Carbon::create($currentYearPeriod->year, 2, 4)->toDateString(),
"comment" => "Comment for the vacation request.",
])
->for($user)
->for($currentYearPeriod)
->create();
$this->stateManager->waitForTechnical($vacationRequest);
Notification::assertSentTo([$user, $technicalApprover, $administrativeApprover],VacationRequestNotification::class);
Notification::assertNotSentTo([$anotherUser],VacationRequestNotification::class);
}
}

View File

@ -5,11 +5,14 @@ declare(strict_types=1);
namespace Tests\Unit;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Carbon;
use Tests\TestCase;
use Tests\Traits\InteractsWithYearPeriods;
use Toby\Domain\Enums\Role;
use Toby\Domain\Enums\VacationRequestState;
use Toby\Domain\Enums\VacationType;
use Toby\Domain\Notifications\VacationRequestNotification;
use Toby\Domain\VacationRequestStateManager;
use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest;