#40 - generate pdf

This commit is contained in:
Adrian Hopek 2022-02-04 10:11:15 +01:00
parent fccf5e97f1
commit f1a3b8f9dd
19 changed files with 161 additions and 35 deletions

View File

@ -22,6 +22,7 @@ use Toby\Domain\Enums\Role;
* @property string $last_name * @property string $last_name
* @property string $email * @property string $email
* @property string $avatar * @property string $avatar
* @property string $position
* @property Role $role * @property Role $role
* @property EmploymentForm $employment_form * @property EmploymentForm $employment_form
* @property Carbon $employment_date * @property Carbon $employment_date

View File

@ -23,7 +23,10 @@ use Toby\Domain\Enums\VacationType;
* @property Carbon $to * @property Carbon $to
* @property string $comment * @property string $comment
* @property User $user * @property User $user
* @property YearPeriod $yearPeriod
* @property Collection $activities * @property Collection $activities
* @property Carbon $created_at
* @property Carbon $updated_at
*/ */
class VacationRequest extends Model class VacationRequest extends Model
{ {
@ -43,6 +46,11 @@ class VacationRequest extends Model
return $this->belongsTo(User::class); return $this->belongsTo(User::class);
} }
public function yearPeriod(): BelongsTo
{
return $this->belongsTo(YearPeriod::class);
}
public function activities(): HasMany public function activities(): HasMany
{ {
return $this->hasMany(VacationRequestActivity::class); return $this->hasMany(VacationRequestActivity::class);

View File

@ -1,18 +0,0 @@
<?php
declare(strict_types=1);
namespace Toby\Infrastructure\Http\Controllers;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\Response;
class GeneratePdfController extends Controller
{
public function generatePDF(): Response
{
$data = ["data"];
$pdf = PDF::loadView('vacation-request-pdf', $data);
return $pdf->stream();
}
}

View File

@ -4,13 +4,16 @@ declare(strict_types=1);
namespace Toby\Infrastructure\Http\Controllers; namespace Toby\Infrastructure\Http\Controllers;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
use Inertia\Response; use Inertia\Response;
use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Enums\VacationRequestState;
use Toby\Domain\Enums\VacationType; use Toby\Domain\Enums\VacationType;
use Toby\Domain\VacationRequestStateManager; use Toby\Domain\VacationRequestStateManager;
use Toby\Domain\Validation\VacationRequestValidator; use Toby\Domain\Validation\VacationRequestValidator;
use Toby\Eloquent\Helpers\YearPeriodRetriever;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
use Toby\Infrastructure\Http\Requests\VacationRequestRequest; use Toby\Infrastructure\Http\Requests\VacationRequestRequest;
use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource; use Toby\Infrastructure\Http\Resources\VacationRequestActivityResource;
@ -18,10 +21,11 @@ use Toby\Infrastructure\Http\Resources\VacationRequestResource;
class VacationRequestController extends Controller class VacationRequestController extends Controller
{ {
public function index(Request $request): Response public function index(Request $request, YearPeriodRetriever $yearPeriodRetriever): Response
{ {
$vacationRequests = $request->user() $vacationRequests = $request->user()
->vacationRequests() ->vacationRequests()
->where("year_period_id", $yearPeriodRetriever->selected()->id)
->latest() ->latest()
->states(VacationRequestState::filterByStatus($request->query("status", "all"))) ->states(VacationRequestState::filterByStatus($request->query("status", "all")))
->paginate(); ->paginate();
@ -40,6 +44,15 @@ class VacationRequestController extends Controller
]); ]);
} }
public function download(VacationRequest $vacationRequest): LaravelResponse
{
$pdf = PDF::loadView("pdf.vacation-request", [
"vacationRequest" => $vacationRequest,
]);
return $pdf->stream();
}
public function create(): Response public function create(): Response
{ {
return inertia("VacationRequest/Create", [ return inertia("VacationRequest/Create", [

View File

@ -19,6 +19,7 @@ class UserRequest extends FormRequest
"lastName" => ["required", "min:3", "max:80"], "lastName" => ["required", "min:3", "max:80"],
"email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)], "email" => ["required", "email", Rule::unique("users", "email")->ignore($this->user)],
"role" => ["required", new Enum(Role::class)], "role" => ["required", new Enum(Role::class)],
"position" => ["required"],
"employmentForm" => ["required", new Enum(EmploymentForm::class)], "employmentForm" => ["required", new Enum(EmploymentForm::class)],
"employmentDate" => ["required", "date_format:Y-m-d"], "employmentDate" => ["required", "date_format:Y-m-d"],
]; ];
@ -30,6 +31,7 @@ class UserRequest extends FormRequest
"first_name" => $this->get("firstName"), "first_name" => $this->get("firstName"),
"last_name" => $this->get("lastName"), "last_name" => $this->get("lastName"),
"email" => $this->get("email"), "email" => $this->get("email"),
"position" => $this->get("position"),
"role" => $this->get("role"), "role" => $this->get("role"),
"employment_form" => $this->get("employmentForm"), "employment_form" => $this->get("employmentForm"),
"employment_date" => $this->get("employmentDate"), "employment_date" => $this->get("employmentDate"),

View File

@ -18,6 +18,7 @@ class UserFormDataResource extends JsonResource
"lastName" => $this->last_name, "lastName" => $this->last_name,
"email" => $this->email, "email" => $this->email,
"role" => $this->role, "role" => $this->role,
"position" => $this->position,
"employmentForm" => $this->employment_form, "employmentForm" => $this->employment_form,
"employmentDate" => $this->employment_date->toDateString(), "employmentDate" => $this->employment_date->toDateString(),
]; ];

View File

@ -17,6 +17,7 @@ class UserResource extends JsonResource
"name" => $this->fullName, "name" => $this->fullName,
"email" => $this->email, "email" => $this->email,
"role" => $this->role->label(), "role" => $this->role->label(),
"position" => $this->position,
"avatar" => asset($this->avatar), "avatar" => asset($this->avatar),
"deleted" => $this->trashed(), "deleted" => $this->trashed(),
"employmentForm" => $this->employment_form->label(), "employmentForm" => $this->employment_form->label(),

View File

@ -22,6 +22,7 @@ class UserFactory extends Factory
"last_name" => $this->faker->lastName(), "last_name" => $this->faker->lastName(),
"email" => $this->faker->unique()->safeEmail(), "email" => $this->faker->unique()->safeEmail(),
"employment_form" => $this->faker->randomElement(EmploymentForm::cases()), "employment_form" => $this->faker->randomElement(EmploymentForm::cases()),
"position" => $this->faker->jobTitle(),
"role" => Role::EMPLOYEE, "role" => Role::EMPLOYEE,
"employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(), "employment_date" => Carbon::createFromInterface($this->faker->dateTimeBetween("2020-10-27"))->toDateString(),
"remember_token" => Str::random(10), "remember_token" => Str::random(10),

View File

@ -4,11 +4,13 @@ declare(strict_types=1);
namespace Database\Factories; namespace Database\Factories;
use Carbon\CarbonImmutable;
use Illuminate\Database\Eloquent\Factories\Factory; use Illuminate\Database\Eloquent\Factories\Factory;
use Toby\Domain\Enums\VacationRequestState; use Toby\Domain\Enums\VacationRequestState;
use Toby\Domain\Enums\VacationType; use Toby\Domain\Enums\VacationType;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\VacationRequest; use Toby\Eloquent\Models\VacationRequest;
use Toby\Eloquent\Models\YearPeriod;
class VacationRequestFactory extends Factory class VacationRequestFactory extends Factory
{ {
@ -16,17 +18,30 @@ class VacationRequestFactory extends Factory
public function definition(): array public function definition(): array
{ {
$number = $this->faker->numberBetween(1, 20); $from = CarbonImmutable::create($this->faker->dateTimeThisYear);
$year = $this->faker->year; $days = $this->faker->numberBetween(0, 20);
return [ return [
"user_id" => User::factory(), "user_id" => User::factory(),
"name" => "{$number}/{$year}", "year_period_id" => YearPeriod::factory(),
"name" => fn(array $attributes) => $this->generateName($attributes),
"type" => $this->faker->randomElement(VacationType::cases()), "type" => $this->faker->randomElement(VacationType::cases()),
"state" => $this->faker->randomElement(VacationRequestState::cases()), "state" => $this->faker->randomElement(VacationRequestState::cases()),
"from" => $this->faker->date, "from" => $from,
"to" => $this->faker->date, "to" => $from->addDays($days),
"comment" => $this->faker->boolean ? $this->faker->paragraph() : null, "comment" => $this->faker->boolean ? $this->faker->paragraph() : null,
]; ];
} }
protected function generateName(array $attributes): string
{
$year = YearPeriod::find($attributes["year_period_id"])->year;
$user = User::find($attributes["user_id"]);
$number = $user->vacationRequests()
->whereYear("from", $year)
->count() + 1;
return "{$number}/{$year}";
}
} }

View File

@ -17,6 +17,7 @@ return new class() extends Migration {
$table->string("email")->unique(); $table->string("email")->unique();
$table->string("avatar")->nullable(); $table->string("avatar")->nullable();
$table->string("role")->default(Role::EMPLOYEE->value); $table->string("role")->default(Role::EMPLOYEE->value);
$table->string("position");
$table->string("employment_form"); $table->string("employment_form");
$table->date("employment_date"); $table->date("employment_date");
$table->rememberToken(); $table->rememberToken();

View File

@ -6,6 +6,7 @@ use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Toby\Eloquent\Models\User; use Toby\Eloquent\Models\User;
use Toby\Eloquent\Models\YearPeriod;
return new class() extends Migration { return new class() extends Migration {
public function up(): void public function up(): void
@ -14,6 +15,7 @@ return new class() extends Migration {
$table->id(); $table->id();
$table->string("name"); $table->string("name");
$table->foreignIdFor(User::class)->constrained()->cascadeOnDelete(); $table->foreignIdFor(User::class)->constrained()->cascadeOnDelete();
$table->foreignIdFor(YearPeriod::class)->constrained()->cascadeOnDelete();
$table->string("type"); $table->string("type");
$table->string("state")->nullable(); $table->string("state")->nullable();
$table->date("from"); $table->date("from");

View File

@ -31,7 +31,6 @@ class DatabaseSeeder extends Seeder
User::factory([ User::factory([
"email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"), "email" => env("LOCAL_EMAIL_FOR_LOGIN_VIA_GOOGLE"),
]) ])
->hasVacationRequests(5)
->create(); ->create();
$users = User::all(); $users = User::all();
@ -70,6 +69,18 @@ class DatabaseSeeder extends Seeder
} }
}) })
->create(); ->create();
$yearPeriods = YearPeriod::all();
foreach ($users as $user) {
VacationRequest::factory()
->count(10)
->for($user)
->sequence(fn() => [
"year_period_id" => $yearPeriods->random()->id,
])
->create();
}
} }
protected function generateAvatarsForUsers(Collection $users): void protected function generateAvatarsForUsers(Collection $users): void

View File

@ -82,6 +82,28 @@
</p> </p>
</div> </div>
</div> </div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="position"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Stanowisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="position"
v-model="form.position"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.position, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.position }"
>
<p
v-if="form.errors.position"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.position }}
</p>
</div>
</div>
<Listbox <Listbox
v-model="form.role" v-model="form.role"
as="div" as="div"
@ -270,6 +292,7 @@ export default {
email: null, email: null,
employmentForm: props.employmentForms[0], employmentForm: props.employmentForms[0],
role: props.roles[0], role: props.roles[0],
position: null,
employmentDate: null, employmentDate: null,
}) })

View File

@ -82,6 +82,28 @@
</p> </p>
</div> </div>
</div> </div>
<div class="sm:grid sm:grid-cols-3 py-4 items-center">
<label
for="position"
class="block text-sm font-medium text-gray-700 sm:mt-px"
>
Stanowisko
</label>
<div class="mt-1 sm:mt-0 sm:col-span-2">
<input
id="position"
v-model="form.position"
class="block w-full max-w-lg shadow-sm rounded-md sm:text-sm"
:class="{ 'border-red-300 text-red-900 focus:outline-none focus:ring-red-500 focus:border-red-500': form.errors.position, 'focus:ring-blumilk-500 focus:border-blumilk-500 sm:text-sm border-gray-300': !form.errors.position }"
>
<p
v-if="form.errors.position"
class="mt-2 text-sm text-red-600"
>
{{ form.errors.position }}
</p>
</div>
</div>
<Listbox <Listbox
v-model="form.role" v-model="form.role"
as="div" as="div"
@ -272,6 +294,7 @@ export default {
lastName: props.user.lastName, lastName: props.user.lastName,
email: props.user.email, email: props.user.email,
role: props.roles.find(role => role.value === props.user.role), role: props.roles.find(role => role.value === props.user.role),
position: props.user.position,
employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm), employmentForm: props.employmentForms.find(form => form.value === props.user.employmentForm),
employmentDate: props.user.employmentDate, employmentDate: props.user.employmentDate,
}) })

View File

@ -49,6 +49,12 @@
> >
Rola Rola
</th> </th>
<th
scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
>
Stanowisko
</th>
<th <th
scope="col" scope="col"
class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider" class="px-6 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider"
@ -97,6 +103,9 @@
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.role }} {{ user.role }}
</td> </td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.position }}
</td>
<td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500"> <td class="px-4 py-4 whitespace-nowrap text-sm text-gray-500">
{{ user.employmentForm }} {{ user.employmentForm }}
</td> </td>

View File

@ -58,6 +58,30 @@
{{ request.comment }} {{ request.comment }}
</dd> </dd>
</div> </div>
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
<dt class="text-sm font-medium text-gray-500">
Załączniki
</dt>
<dd class="mt-1 text-sm text-gray-900 sm:mt-0 sm:col-span-2">
<ul class="border border-gray-200 rounded-md divide-y divide-gray-200">
<li class="pl-3 pr-4 py-3 flex items-center justify-between text-sm">
<div class="w-0 flex-1 flex items-center">
<PaperClipIcon class="flex-shrink-0 h-5 w-5 text-gray-400" />
<span class="ml-2 flex-1 w-0 truncate"> wniosek_urlopowy.pdf </span>
</div>
<div class="ml-4 flex-shrink-0">
<a
:href="`/vacation-requests/${request.id}/download`"
target="_blank"
class="font-medium text-indigo-600 hover:text-indigo-500"
>
Pobierz
</a>
</div>
</li>
</ul>
</dd>
</div>
</dl> </dl>
</div> </div>
</div> </div>
@ -196,11 +220,13 @@
<script> <script>
import { ThumbUpIcon } from '@heroicons/vue/outline' import { ThumbUpIcon } from '@heroicons/vue/outline'
import {PaperClipIcon} from '@heroicons/vue/solid'
export default { export default {
name: 'VacationRequestShow', name: 'VacationRequestShow',
components: { components: {
ThumbUpIcon, ThumbUpIcon,
PaperClipIcon,
}, },
props: { props: {
request: { request: {

View File

@ -54,14 +54,14 @@
<table> <table>
<tbody> <tbody>
<tr> <tr>
<td>Jan Kowalski</td> <td>{{ $vacationRequest->user->fullName }}</td>
<td>Legnica, 02.02.2022</td> <td>Legnica, {{ $vacationRequest->created_at->format("d.m.Y") }}</td>
</tr> </tr>
<tr> <tr>
<td class="helper-text">imię i nazwisko</td> <td class="helper-text">imię i nazwisko</td>
</tr> </tr>
<tr> <tr>
<td>tester oprogramowania</td> <td>{{ $vacationRequest->user->position }}</td>
</tr> </tr>
<tr> <tr>
<td class="helper-text">stanowisko</td> <td class="helper-text">stanowisko</td>
@ -72,15 +72,17 @@
<div class="main"> <div class="main">
<h2>Wniosek o urlop</h2> <h2>Wniosek o urlop</h2>
<p class="content">Proszę o udzielenie urlopu wypoczynkowego w okresie od dnia 03.02.2022 do dnia 10.02.2022 <p class="content">
włącznie tj. 5 dni roboczych za rok 2022. </p> Proszę o {{ mb_strtolower($vacationRequest->type->label()) }} w okresie od dnia {{ $vacationRequest->from->format("d.m.Y") }}
do dnia {{ $vacationRequest->to->format("d.m.Y") }} włącznie tj. x dni roboczych za rok {{ $vacationRequest->yearPeriod->year }}.
</p>
</div> </div>
<table class="signatureTable"> <table class="signatureTable">
<tbody> <tbody>
<tr> <tr>
<td>Super Przełożony</td> <td>........................</td>
<td>Jan Kowalski</td> <td>........................</td>
</tr> </tr>
<tr> <tr>
<td class="helper-text">podpis przełożonego</td> <td class="helper-text">podpis przełożonego</td>

View File

@ -3,7 +3,6 @@
declare(strict_types=1); declare(strict_types=1);
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Toby\Infrastructure\Http\Controllers\GeneratePdfController;
use Toby\Infrastructure\Http\Controllers\GoogleController; use Toby\Infrastructure\Http\Controllers\GoogleController;
use Toby\Infrastructure\Http\Controllers\HolidayController; use Toby\Infrastructure\Http\Controllers\HolidayController;
use Toby\Infrastructure\Http\Controllers\LogoutController; use Toby\Infrastructure\Http\Controllers\LogoutController;
@ -34,6 +33,8 @@ Route::middleware("auth")->group(function (): void {
->name("vacation.requests.store"); ->name("vacation.requests.store");
Route::get("/vacation-requests/{vacationRequest}", [VacationRequestController::class, "show"]) Route::get("/vacation-requests/{vacationRequest}", [VacationRequestController::class, "show"])
->name("vacation.requests.show"); ->name("vacation.requests.show");
Route::get("/vacation-requests/{vacationRequest}/download", [VacationRequestController::class, "download"])
->name("vacation.requests.download");
Route::post("/vacation-requests/{vacationRequest}/reject", [VacationRequestController::class, "reject"]) Route::post("/vacation-requests/{vacationRequest}/reject", [VacationRequestController::class, "reject"])
->name("vacation.requests.reject"); ->name("vacation.requests.reject");
Route::post("/vacation-requests/{vacationRequest}/cancel", [VacationRequestController::class, "cancel"]) Route::post("/vacation-requests/{vacationRequest}/cancel", [VacationRequestController::class, "cancel"])
@ -45,8 +46,6 @@ Route::middleware("auth")->group(function (): void {
Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class) Route::post("year-periods/{yearPeriod}/select", SelectYearPeriodController::class)
->name("year-periods.select"); ->name("year-periods.select");
Route::get("/generate-pdf", [GeneratePdfController::class, "generatePDF"]);
}); });
Route::middleware("guest")->group(function (): void { Route::middleware("guest")->group(function (): void {

View File

@ -89,6 +89,7 @@ class UserTest extends FeatureTestCase
"firstName" => "John", "firstName" => "John",
"lastName" => "Doe", "lastName" => "Doe",
"role" => Role::EMPLOYEE->value, "role" => Role::EMPLOYEE->value,
"position" => "Test position",
"email" => "john.doe@example.com", "email" => "john.doe@example.com",
"employmentForm" => EmploymentForm::B2B_CONTRACT->value, "employmentForm" => EmploymentForm::B2B_CONTRACT->value,
"employmentDate" => Carbon::now()->toDateString(), "employmentDate" => Carbon::now()->toDateString(),
@ -99,6 +100,8 @@ class UserTest extends FeatureTestCase
"first_name" => "John", "first_name" => "John",
"last_name" => "Doe", "last_name" => "Doe",
"email" => "john.doe@example.com", "email" => "john.doe@example.com",
"role" => Role::EMPLOYEE->value,
"position" => "Test position",
"employment_form" => EmploymentForm::B2B_CONTRACT->value, "employment_form" => EmploymentForm::B2B_CONTRACT->value,
"employment_date" => Carbon::now()->toDateString(), "employment_date" => Carbon::now()->toDateString(),
]); ]);
@ -125,6 +128,7 @@ class UserTest extends FeatureTestCase
"lastName" => "Doe", "lastName" => "Doe",
"email" => "john.doe@example.com", "email" => "john.doe@example.com",
"role" => Role::EMPLOYEE->value, "role" => Role::EMPLOYEE->value,
"position" => "Test position",
"employmentForm" => EmploymentForm::B2B_CONTRACT->value, "employmentForm" => EmploymentForm::B2B_CONTRACT->value,
"employmentDate" => Carbon::now()->toDateString(), "employmentDate" => Carbon::now()->toDateString(),
]) ])
@ -134,6 +138,8 @@ class UserTest extends FeatureTestCase
"first_name" => "John", "first_name" => "John",
"last_name" => "Doe", "last_name" => "Doe",
"email" => "john.doe@example.com", "email" => "john.doe@example.com",
"role" => Role::EMPLOYEE->value,
"position" => "Test position",
"employment_form" => EmploymentForm::B2B_CONTRACT->value, "employment_form" => EmploymentForm::B2B_CONTRACT->value,
"employment_date" => Carbon::now()->toDateString(), "employment_date" => Carbon::now()->toDateString(),
]); ]);