#41 - wip
This commit is contained in:
parent
d026d41715
commit
a0b64e6cb3
@ -1,4 +1,4 @@
|
||||
APP_NAME="Toby HR applicaiton"
|
||||
APP_NAME="Toby HR application"
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
|
67
app/Domain/Notifications/VacationRequestNotification.php
Normal file
67
app/Domain/Notifications/VacationRequestNotification.php
Normal 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);
|
||||
}
|
||||
}
|
31
app/Domain/VacationRequestNotificationSender.php
Normal file
31
app/Domain/VacationRequestNotificationSender.php
Normal 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();
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -35,7 +35,7 @@ return [
|
||||
"name" => env("MAIL_FROM_NAME", "Example"),
|
||||
],
|
||||
"markdown" => [
|
||||
"theme" => "default",
|
||||
"theme" => "mail",
|
||||
"paths" => [
|
||||
resource_path("views/vendor/mail"),
|
||||
],
|
||||
|
@ -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"
|
||||
}
|
||||
|
286
resources/views/vendor/mail/html/themes/mail.css
vendored
Normal file
286
resources/views/vendor/mail/html/themes/mail.css
vendored
Normal 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;
|
||||
}
|
94
tests/Unit/VacationRequestNotificationTest.php
Normal file
94
tests/Unit/VacationRequestNotificationTest.php
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user