Add efz document to members

This commit is contained in:
philipp lang 2022-03-20 16:33:56 +01:00
parent 51b0ffef72
commit fc155bdf55
27 changed files with 472 additions and 55 deletions

View File

@ -84,6 +84,13 @@ class Member extends Model
return $this->firstname.' '.$this->lastname;
}
public function getEfzLink(): ?string
{
return $this->memberships()->whereHas('activity', fn (Builder $query) => $query->where('has_efz', true))->exists()
? route('efz', ['member' => $this])
: null;
}
public function getHasNamiAttribute(): bool
{
return null !== $this->nami_id;

View File

@ -62,6 +62,7 @@ class MemberResource extends JsonResource
'age_group_icon' => $this->age_group_icon,
'courses' => CourseResource::collection($this->whenLoaded('courses')),
'efz' => $this->efz,
'efz_link' => $this->getEfzLink(),
];
}
}

View File

@ -2,7 +2,7 @@
namespace App\Payment;
use App\Pdf\PdfRepository;
use App\Pdf\LetterRepository;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
@ -13,7 +13,7 @@ class PaymentMail extends Mailable
use Queueable;
use SerializesModels;
public PdfRepository $repo;
public LetterRepository $repo;
public string $filename;
/**
@ -21,7 +21,7 @@ class PaymentMail extends Mailable
*
* @return void
*/
public function __construct(PdfRepository $repo, string $filename)
public function __construct(LetterRepository $repo, string $filename)
{
$this->filename = $filename;
$this->repo = $repo;

View File

@ -6,7 +6,7 @@ use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Support\Collection;
class BillType extends Repository implements PdfRepository
class BillType extends Repository implements LetterRepository
{
public string $filename;
@ -25,13 +25,18 @@ class BillType extends Repository implements PdfRepository
return 'Rechnung';
}
public function setFilename(string $filename): self
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::XELATEX;
}
public function getFilename(): string
{
return $this->filename;

View File

@ -0,0 +1,68 @@
<?php
namespace App\Pdf\Data;
use App\Member\Member;
use App\Pdf\EnvType;
use App\Pdf\PdfRepository;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Spatie\LaravelData\Data;
class MemberEfzData extends Data implements PdfRepository
{
public function __construct(
public ?string $name,
public ?string $secondLine,
public ?string $currentDate,
public ?array $sender = [],
public ?string $filename = '',
) {
}
public static function fromRequest(Request $request): self
{
$memberId = $request->member;
$member = Member::findOrFail($memberId);
return new self(
name: $member->fullname,
secondLine: "geb. am {$member->birthday->format('d.m.Y')}, wohnhaft in {$member->location}",
currentDate: now()->format('d.m.Y'),
sender: [
$member->fullname,
$member->address,
$member->zip.' '.$member->location,
'Mglnr.: '.$member->nami_id,
]
);
}
public function getFilename(): string
{
return 'efz-fuer-'.Str::slug($this->name);
}
public function getView(): string
{
return 'tex.efz';
}
public function getTemplate(): string
{
return 'efz';
}
public function setFilename(string $filename): static
{
$this->filename = $filename;
return $this;
}
public function getScript(): EnvType
{
return EnvType::PDFLATEX;
}
}

9
app/Pdf/EnvType.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace App\Pdf;
enum EnvType: string
{
case XELATEX = 'XELATEX';
case PDFLATEX = 'PDFLATEX';
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Pdf;
use App\Member\Member;
use App\Payment\Payment;
use Carbon\Carbon;
use Generator;
use Illuminate\Support\Collection;
interface LetterRepository extends PdfRepository
{
public function getSubject(): string;
public function getPositions(Collection $page): array;
public function getFamilyName(Collection $page): string;
public function getAddress(Collection $page): string;
public function getZip(Collection $page): string;
public function getLocation(Collection $page): string;
public function createable(Member $member): bool;
public function getPayments(Member $member): Collection;
public function linkLabel(): string;
public function getUntil(): Carbon;
public function getUsage(Collection $page): string;
public function allLabel(): string;
public function getEmail(Collection $page): string;
public function getDescription(): array;
public function afterSingle(Payment $payment): void;
public function getMailSubject(): string;
public function allPayments(): Generator;
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Pdf;
use App\Http\Controllers\Controller;
use App\Pdf\Data\MemberEfzData;
class MemberEfzController extends Controller
{
public function __invoke(MemberEfzData $data): PdfGenerator
{
return app(PdfGenerator::class)->setRepository($data)->render();
}
}

View File

@ -30,7 +30,8 @@ class PdfGenerator implements Responsable
$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';
$command .= ' && '.env($this->repo->getScript()->value).' --halt-on-error '.$this->repo->getFilename().'.tex';
$command .= ' && '.env($this->repo->getScript()->value).' --halt-on-error '.$this->repo->getFilename().'.tex';
exec($command, $output, $returnVar);
return $this;

View File

@ -2,17 +2,9 @@
namespace App\Pdf;
use App\Member\Member;
use App\Payment\Payment;
use Carbon\Carbon;
use Generator;
use Illuminate\Support\Collection;
interface PdfRepository
{
public function getSubject(): string;
public function setFilename(string $filename): self;
public function setFilename(string $filename): static;
public function getFilename(): string;
@ -20,35 +12,5 @@ interface PdfRepository
public function getTemplate(): string;
public function getPositions(Collection $page): array;
public function getFamilyName(Collection $page): string;
public function getAddress(Collection $page): string;
public function getZip(Collection $page): string;
public function getLocation(Collection $page): string;
public function createable(Member $member): bool;
public function getPayments(Member $member): Collection;
public function linkLabel(): string;
public function getUntil(): Carbon;
public function getUsage(Collection $page): string;
public function allLabel(): string;
public function getEmail(Collection $page): string;
public function getDescription(): array;
public function afterSingle(Payment $payment): void;
public function getMailSubject(): string;
public function allPayments(): Generator;
public function getScript(): EnvType;
}

View File

@ -22,10 +22,10 @@ class PdfRepositoryFactory
*/
public function getTypes(): Collection
{
return collect(array_map(fn ($classString) => new $classString(), $this->types));
return collect(array_map(fn ($classString) => new $classString(collect()), $this->types));
}
public function fromSingleRequest(string $type, Member $member): ?PdfRepository
public function fromSingleRequest(string $type, Member $member): ?LetterRepository
{
$members = $this->singleMemberCollection($member, $type);
@ -63,7 +63,7 @@ class PdfRepositoryFactory
});
}
public function afterSingle(PdfRepository $repo): void
public function afterSingle(LetterRepository $repo): void
{
foreach ($repo->allPayments() as $payment) {
$repo->afterSingle($payment);
@ -94,7 +94,7 @@ class PdfRepositoryFactory
->filter(fn (Member $member) => app($type)->createable($member));
}
private function resolve(string $kind, Collection $members): PdfRepository
private function resolve(string $kind, Collection $members): LetterRepository
{
return new $kind($members);
}

View File

@ -6,7 +6,7 @@ use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Support\Collection;
class RememberType extends Repository implements PdfRepository
class RememberType extends Repository implements LetterRepository
{
public string $filename;
@ -25,7 +25,7 @@ class RememberType extends Repository implements PdfRepository
return 'Zahlungserinnerung';
}
public function setFilename(string $filename): self
public function setFilename(string $filename): static
{
$this->filename = $filename;
@ -37,6 +37,11 @@ class RememberType extends Repository implements PdfRepository
return $this->filename;
}
public function getScript(): EnvType
{
return EnvType::XELATEX;
}
public function getView(): string
{
return 'tex.remember';

View File

@ -21,6 +21,7 @@
"laravel/telescope": "^4.0",
"laravel/tinker": "^2.0",
"laravel/ui": "^3.0",
"spatie/laravel-data": "^1.4",
"spatie/laravel-medialibrary": "^10.0",
"spatie/laravel-settings": "^2.2",
"zoomyboy/laravel-nami": "dev-master"

137
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "7093b1cd1d24ebeebcdaf1e751272835",
"content-hash": "ad3e33990f66a57dd610875f8ba971ae",
"packages": [
{
"name": "brick/math",
@ -4036,6 +4036,82 @@
},
"time": "2021-12-21T10:08:05+00:00"
},
{
"name": "spatie/laravel-data",
"version": "1.4.5",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-data.git",
"reference": "9607caa7d3f75a653c7de28ce574f31358d410dc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-data/zipball/9607caa7d3f75a653c7de28ce574f31358d410dc",
"reference": "9607caa7d3f75a653c7de28ce574f31358d410dc",
"shasum": ""
},
"require": {
"illuminate/contracts": "^8.71|^9.0",
"php": "^8.0",
"phpdocumentor/type-resolver": "^1.5",
"spatie/laravel-package-tools": "^1.9.0"
},
"require-dev": {
"brianium/paratest": "^6.2",
"fakerphp/faker": "^1.14",
"friendsofphp/php-cs-fixer": "^3.0",
"nette/php-generator": "^3.5",
"nunomaduro/collision": "^5.3|^6.0",
"orchestra/testbench": "^6.23|^7.0",
"phpunit/phpunit": "^9.3",
"spatie/laravel-typescript-transformer": "^2.0",
"spatie/phpunit-snapshot-assertions": "^4.2",
"spatie/test-time": "^1.2",
"vimeo/psalm": "^4.4"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\LaravelData\\LaravelDataServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Spatie\\LaravelData\\": "src",
"Spatie\\LaravelData\\Database\\Factories\\": "database/factories"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ruben Van Assche",
"email": "ruben@spatie.be",
"role": "Developer"
}
],
"description": "Create unified resources and data transfer objects",
"homepage": "https://github.com/spatie/laravel-data",
"keywords": [
"laravel",
"laravel-data",
"spatie"
],
"support": {
"source": "https://github.com/spatie/laravel-data/tree/1.4.5"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2022-03-18T14:42:34+00:00"
},
{
"name": "spatie/laravel-medialibrary",
"version": "10.1.3",
@ -4144,6 +4220,65 @@
],
"time": "2022-03-10T14:39:11+00:00"
},
{
"name": "spatie/laravel-package-tools",
"version": "1.11.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/laravel-package-tools.git",
"reference": "baeb3df0ebb3a541394fdaf8cbe6115bf4034a59"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/baeb3df0ebb3a541394fdaf8cbe6115bf4034a59",
"reference": "baeb3df0ebb3a541394fdaf8cbe6115bf4034a59",
"shasum": ""
},
"require": {
"illuminate/contracts": "^7.0|^8.0|^9.0",
"php": "^7.4|^8.0"
},
"require-dev": {
"mockery/mockery": "^1.4",
"orchestra/testbench": "^5.0|^6.23|^7.0",
"phpunit/phpunit": "^9.4",
"spatie/test-time": "^1.2"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\LaravelPackageTools\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
"role": "Developer"
}
],
"description": "Tools for creating Laravel packages",
"homepage": "https://github.com/spatie/laravel-package-tools",
"keywords": [
"laravel-package-tools",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/laravel-package-tools/issues",
"source": "https://github.com/spatie/laravel-package-tools/tree/1.11.3"
},
"funding": [
{
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2022-03-15T20:01:36+00:00"
},
{
"name": "spatie/laravel-settings",
"version": "2.3.2",

View File

@ -5,6 +5,9 @@ namespace Database\Factories;
use App\Group;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Group>
*/
class GroupFactory extends Factory
{
protected $model = Group::class;

View File

@ -0,0 +1,28 @@
<?php
namespace Database\Factories\Member;
use App\Group;
use App\Member\Membership;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* @extends Factory<Membership>
*/
class MembershipFactory extends Factory
{
public $model = Membership::class;
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition()
{
return [
'group_id' => Group::factory()->createOne()->id,
'from' => now()->subMonths(3),
];
}
}

View File

@ -13,7 +13,7 @@ return new class() extends Migration {
public function up()
{
Schema::table('members', function (Blueprint $table) {
$table->date('efz')->nullable();
$table->date('efz')->after('subscription_id')->nullable();
});
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class() extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('activities', function (Blueprint $table) {
$table->boolean('has_efz')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('activities', function (Blueprint $table) {
$table->dropColumn('has_efz');
});
}
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 17v-2m3 2v-4m3 4v-6m2 10H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 0 1 .707.293l5.414 5.414a1 1 0 0 1 .293.707V19a2 2 0 0 1-2 2z"/></svg>

After

Width:  |  Height:  |  Size: 326 B

View File

@ -55,6 +55,7 @@
<a href="#" v-show="hasModule('bill')" @click.prevent="openSidebar(index, 'payment.index')" class="inline-flex btn btn-info btn-sm"><svg-sprite src="money"></svg-sprite></a>
<a href="#" v-show="hasModule('courses')" @click.prevent="openSidebar(index, 'courses.index')" class="inline-flex btn btn-info btn-sm"><svg-sprite src="course"></svg-sprite></a>
<a href="#" @click.prevent="openSidebar(index, 'membership.index')" class="inline-flex btn btn-info btn-sm"><svg-sprite src="user"></svg-sprite></a>
<a :href="member.efz_link" v-show="member.efz_link" class="inline-flex btn btn-info btn-sm"><svg-sprite src="report"></svg-sprite></a>
<i-link href="#" @click.prevent="remove(member)" class="inline-flex btn btn-danger btn-sm"><svg-sprite src="trash"></svg-sprite></i-link>
</td>
</tr>

View File

@ -0,0 +1,48 @@
\documentclass[a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage[lf]{Baskervaldx} % lining figures
\usepackage[bigdelims,vvarbb]{newtxmath} % math italic letters from Nimbus Roman
\usepackage[cal=boondoxo]{mathalfa} % mathcal from STIX, unslanted a bit
\renewcommand*\oldstylenums[1]{\textosf{#1}}
\usepackage{background}
\usepackage[margin=0cm]{geometry}
\usetikzlibrary{calc}
\backgroundsetup{scale = 1, pages=some, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {bg-1.pdf}}}
\begin{document}
\sffamily
\noindent \begin{tikzpicture}[yscale=-1, remember picture, overlay] \end{tikzpicture}
\pagebreak
\begin{tikzpicture}[yscale=-1, remember picture, overlay]
% \useasboundingbox (2.52,6.67) rectangle (18.47,12.85);
% \fill[fill=white, opacity=0.9] (25mm,63mm) rectangle ++(160mm,62mm);
\coordinate (A) at (19mm, 128.0mm);
\coordinate (B) at ($(A)+(0mm,5mm)$);
\coordinate (C) at (19mm,212mm);
\path (A) node[anchor=west,fill=white,text width=120mm,text height=2.5mm,inner xsep=0mm,inner ysep=1mm]{<<<$data->name>>>};
\path (B) node[anchor=west,fill=white,text width=120mm,text height=2.5mm,inner xsep=0mm,inner ysep=1mm]{<<<$data->secondLine>>>};
\path (C) node[anchor=west,fill=white,text width=120mm,text height=2.5mm,inner xsep=0mm,inner ysep=1mm]{Neuss, <<<$data->currentDate>>>};
\end{tikzpicture}
\backgroundsetup{scale = 1, pages=some, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {bg-2.pdf}}}
\pagebreak
\backgroundsetup{scale = 1, pages=some, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {bg-3.pdf}}}
\begin{tikzpicture}[yscale=-1, remember picture, overlay]
% \useasboundingbox (2.52,6.67) rectangle (18.47,12.85);
% \fill[fill=white, opacity=0.9] (25mm,63mm) rectangle ++(160mm,62mm);
\node[anchor=north west,fill=white,text width=80mm,minimum height=30mm,inner sep=1mm] at (110mm, 42.0mm) {Absender: \vspace{2mm} \\ <<<implode(' \\\\ ', $data->sender)>>>};
\end{tikzpicture}
\end{document}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -10,10 +10,11 @@ use App\Payment\AllpaymentController;
use App\Payment\PaymentController;
use App\Payment\SendpaymentController;
use App\Payment\SubscriptionController;
use App\Pdf\MemberEfzController;
use App\Pdf\MemberPdfController;
use App\Setting\Controllers\SettingController;
Route::group(['namespace' => 'App\\Http\\Controllers'], function(): void {
Route::group(['namespace' => 'App\\Http\\Controllers'], function (): void {
Auth::routes(['register' => false]);
});
@ -32,4 +33,5 @@ Route::group(['middleware' => 'auth:web'], function (): void {
Route::apiResource('member.membership', MembershipController::class);
Route::resource('setting', SettingController::class);
Route::resource('member.course', CourseController::class);
Route::get('/member/{member}/efz', MemberEfzController::class)->name('efz');
});

View File

@ -2,9 +2,12 @@
namespace Tests\Feature\Member;
use App\Activity;
use App\Course\Models\Course;
use App\Course\Models\CourseMember;
use App\Member\Member;
use App\Member\Membership;
use App\Subactivity;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\LaravelNami\Backend\FakeBackend;
@ -43,4 +46,27 @@ class IndexTest extends TestCase
$this->assertComponent('member/VIndex', $response);
$this->assertInertiaHas('::firstname::', $response, 'data.data.0.firstname');
}
public function testItShowsEfzForEfzMembership(): void
{
$this->withoutExceptionHandling();
$this->login();
$member = Member::factory()
->defaults()
->has(Membership::factory()->for(Subactivity::factory())->for(Activity::factory()->state(['has_efz' => true])))
->create(['lastname' => 'A']);
Member::factory()
->defaults()
->has(Membership::factory()->for(Subactivity::factory())->for(Activity::factory()->state(['has_efz' => false])))
->create(['lastname' => 'B']);
Member::factory()
->defaults()
->create(['lastname' => 'C']);
$response = $this->get('/member');
$this->assertInertiaHas(url("/member/{$member->id}/efz"), $response, 'data.data.0.efz_link');
$this->assertInertiaHas(null, $response, 'data.data.1.efz_link');
$this->assertInertiaHas(null, $response, 'data.data.2.efz_link');
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Tests\Feature\Pdf;
use App\Member\Member;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class EfzTest extends TestCase
{
use DatabaseTransactions;
public function testItCreatesAEfzPdfFile(): void
{
$this->withoutExceptionHandling()->login();
$member = Member::factory()->defaults()->create(['firstname' => 'Max', 'lastname' => 'Muster']);
$response = $this->get("/member/{$member->id}/efz");
$response->assertOk();
$response->assertHeader('Content-Disposition', 'inline; filename="efz-fuer-max-muster.pdf"');
}
}