Add pdf generator

This commit is contained in:
philipp lang 2021-07-16 00:12:19 +02:00
parent 7c190d7bcf
commit 3406cf22f9
16 changed files with 129 additions and 58 deletions

2
.gitignore vendored
View File

@ -16,8 +16,8 @@ npm-debug.log
yarn-error.log yarn-error.log
tags tags
/resources/js/libs /resources/js/libs
/packages
/public/vendor /public/vendor
/storage/temp
# Temporary files # Temporary files
*.swp *.swp

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "packages/silvaletter"]
path = packages/silvaletter
url = git@zoomyboy.de:silvaletter.git

View File

@ -2,12 +2,12 @@
namespace App\Http\Views; namespace App\Http\Views;
use App\Member\MemberResource;
use App\Member\Member; use App\Member\Member;
use Illuminate\Http\Request; use App\Member\MemberResource;
use App\Payment\PaymentResource;
use App\Payment\Status; use App\Payment\Status;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Payment\PaymentResource; use Illuminate\Http\Request;
class MemberView { class MemberView {
public function index(Request $request) { public function index(Request $request) {

View File

@ -31,4 +31,14 @@ class BillType implements PdfRepository
return $this->filename; return $this->filename;
} }
public function getView(): string
{
return 'tex.bill';
}
public function getTemplate(): string
{
return 'default';
}
} }

View File

@ -15,7 +15,7 @@ class MemberPdfController extends Controller
return $repo === null return $repo === null
? response()->noContent() ? response()->noContent()
: app(PdfGenerator::class)->render($repo); : app(PdfGenerator::class)->setRepository($repo)->render();
} }
} }

View File

@ -3,29 +3,64 @@
namespace App\Pdf; namespace App\Pdf;
use Illuminate\Contracts\Support\Responsable; use Illuminate\Contracts\Support\Responsable;
use Illuminate\Support\Str;
use Storage; use Storage;
class PdfGenerator implements Responsable class PdfGenerator implements Responsable
{ {
private ?string $filename = null; private ?string $filename = null;
private PdfRepository $repo;
private string $dir;
public function render(PdfRepository $repo): self public function setRepository(PdfRepository $repo): self
{ {
$content = view()->make('pdf.pdf', [ $this->repo = $repo;
'data' => $repo,
return $this;
}
public function render(): self
{
$this->filename = $this->repo->getFilename();
$this->dir = Str::random(32);
Storage::disk('temp')->put($this->dir.'/'.$this->repo->getFilename().'.tex', $this->compileView());
Storage::disk('temp')->makeDirectory($this->dir);
$this->copyTemplateTo(Storage::disk('temp')->path($this->dir));
$command = 'cd '.Storage::disk('temp')->path($this->dir);
$command .= ' && '.env('XELATEX').' --halt-on-error '.$this->repo->getFilename().'.tex';
exec($command, $output, $returnVar);
return $this;
}
public function compileView(): string
{
return (string) view()->make($this->repo->getView(), [
'data' => $this->repo,
]); ]);
$filename = $repo->getFilename();
dd($filename);
Storage::disk('temp')->put($repo->getBasename(), $content);
} }
public function toResponse($request) public function toResponse($request)
{ {
return response()->file($this->filename); return response()->file($this->getCompiledFilename(), [
'Content-Type' => 'application/pdf',
'Content-Disposition' => "inline; filename=\"{$this->filename}.pdf\"",
]);
}
public function getCompiledFilename(): string
{
return Storage::disk('temp')->path($this->dir.'/'.$this->filename.'.pdf');
}
private function copyTemplateTo(string $destination): void
{
$templatePath = resource_path("views/tex/templates/{$this->repo->getTemplate()}");
exec('cp '.$templatePath.'/* '.$destination);
} }
} }

View File

@ -13,4 +13,8 @@ interface PdfRepository
public function getFilename(): string; public function getFilename(): string;
public function getView(): string;
public function getTemplate(): string;
} }

View File

@ -176,6 +176,7 @@ return [
App\Providers\HorizonServiceProvider::class, App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
App\Providers\TelescopeServiceProvider::class, App\Providers\TelescopeServiceProvider::class,
App\Tex\TexServiceProvider::class,
], ],

View File

@ -48,6 +48,11 @@ return [
'root' => storage_path('app'), 'root' => storage_path('app'),
], ],
'temp' => [
'driver' => 'local',
'root' => storage_path('temp'),
],
'public' => [ 'public' => [
'driver' => 'local', 'driver' => 'local',
'root' => storage_path('app/public'), 'root' => storage_path('app/public'),

1
packages/silvaletter Submodule

@ -0,0 +1 @@
Subproject commit 63fb0c9daed5648bc2bf06a0361d95cd586db17f

View File

@ -1,6 +1,6 @@
\documentclass[silvaletter,12pt]{scrlttr2} \documentclass[silvaletter,12pt]{scrlttr2}
\setkomavar{subject}{<-- $subject -->} \setkomavar{subject}{Rechnung}
\begin{document} \begin{document}
\begin{letter}{Familie Charnay\\Junkerstr 4\\42699 Solingen} \begin{letter}{Familie Charnay\\Junkerstr 4\\42699 Solingen}

View File

@ -0,0 +1 @@
../../../../packages/silvaletter/template

View File

@ -21,6 +21,7 @@ Route::group(['middleware' => 'auth:web'], function () {
Route::resource('allpayment', AllpaymentController::class); Route::resource('allpayment', AllpaymentController::class);
Route::resource('subscription', SubscriptionController::class); Route::resource('subscription', SubscriptionController::class);
Route::post('/member/{member}/confirm', MemberConfirmController::class); Route::post('/member/{member}/confirm', MemberConfirmController::class);
Route::post('/member/{member}/pdf', MemberPdfController::class)->name('member.singlepdf'); Route::get('/member/{member}/pdf', MemberPdfController::class)
->name('member.singlepdf');
}); });

0
storage/temp/.gitkeep Normal file
View File

View File

@ -10,6 +10,8 @@ use App\Nationality;
use App\Payment\Payment; use App\Payment\Payment;
use App\Payment\Subscription; use App\Payment\Subscription;
use App\Pdf\BillType; use App\Pdf\BillType;
use App\Pdf\PdfGenerator;
use App\Pdf\PdfRepositoryFactory;
use Database\Factories\Member\MemberFactory; use Database\Factories\Member\MemberFactory;
use Database\Factories\Payment\PaymentFactory; use Database\Factories\Payment\PaymentFactory;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@ -22,13 +24,11 @@ class GenerateTest extends TestCase
{ {
use RefreshDatabase; use RefreshDatabase;
use FakesTex;
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->fakeTex();
Storage::fake('temp'); Storage::fake('temp');
} }
@ -63,7 +63,7 @@ class GenerateTest extends TestCase
'type' => BillType::class, 'type' => BillType::class,
'filename' => 'rechnung-fur-firstname-lastname.pdf', 'filename' => 'rechnung-fur-firstname-lastname.pdf',
'output' => [ 'output' => [
'12,00 €', '12.00',
'Familie ::lastname::', 'Familie ::lastname::',
], ],
], ],
@ -80,8 +80,55 @@ class GenerateTest extends TestCase
): void { ): void {
$this->withoutExceptionHandling(); $this->withoutExceptionHandling();
$this->login(); $this->login();
$members = $this->setupMembers($members);
$members = collect($members)->map(function (array $member): Member { $urlId = call_user_func($urlCallable, $members);
$response = $this->call('GET', "/member/{$urlId}/pdf", [
'type' => $type,
]);
if ($filename === null) {
$response->assertStatus(204);
return;
}
$this->assertEquals('application/pdf', $response->headers->get('content-type'));
$this->assertEquals('inline; filename="' . $filename . '"', $response->headers->get('content-disposition'));
}
/** @dataProvider generatorProvider */
public function testItGeneratesTheLayout(
array $members,
callable $urlCallable,
string $type,
?string $filename = null,
?array $output = null
): void {
$this->withoutExceptionHandling();
$this->login();
$members = $this->setupMembers($members);
$urlId = call_user_func($urlCallable, $members);
$member = Member::find($urlId);
$repo = app(PdfRepositoryFactory::class)->fromSingleRequest($type, $member);
if ($filename === null) {
$this->assertNull($repo);
return;
}
$content = app(PdfGenerator::class)->setRepository($repo)->compileView();
foreach ($output as $out) {
$this->assertStringContainsString($out, $content);
}
}
private function setupMembers(array $members): Collection
{
return collect($members)->map(function (array $member): Member {
$memberFactory = Member::factory() $memberFactory = Member::factory()
->for(Nationality::factory()) ->for(Nationality::factory())
->for(Subscription::factory()->for(Fee::factory())) ->for(Subscription::factory()->for(Fee::factory()))
@ -97,25 +144,6 @@ class GenerateTest extends TestCase
return $memberModel->load('payments'); return $memberModel->load('payments');
}); });
$urlId = call_user_func($urlCallable, $members);
$response = $this->post("/member/{$urlId}/pdf", [
'type' => $type,
]);
if ($filename === null) {
$response->assertStatus(204);
$this->assertTexCount(0);
return;
}
$this->assertEquals('application/pdf', $response->headers->get('content-type'));
$this->assertTrue('attachment; filename="' . $filename . '"', $response->headers->get('content-disposition'));
foreach ($output as $out) {
$this->assertTexGeneratedWith($out);
}
} }
} }

View File

@ -1,18 +0,0 @@
<?php
namespace Tests\Traits;
trait FakesTex
{
public function fakeTex(): void
{
}
public function assertTexCount(int $count): void
{
}
}