diff --git a/app/Contribution/ContributionFactory.php b/app/Contribution/ContributionFactory.php index 0db0aab1..ebfd6f39 100644 --- a/app/Contribution/ContributionFactory.php +++ b/app/Contribution/ContributionFactory.php @@ -8,6 +8,7 @@ use App\Contribution\Documents\RdpNrwDocument; use App\Contribution\Documents\CityRemscheidDocument; use App\Contribution\Documents\CitySolingenDocument; use App\Contribution\Documents\CityFrankfurtMainDocument; +use App\Contribution\Documents\GallierDocument; use Illuminate\Support\Collection; use Illuminate\Validation\Rule; @@ -22,6 +23,7 @@ class ContributionFactory CityRemscheidDocument::class, CityFrankfurtMainDocument::class, BdkjHesse::class, + GallierDocument::class, ]; /** diff --git a/app/Contribution/Documents/GallierDocument.php b/app/Contribution/Documents/GallierDocument.php new file mode 100644 index 00000000..520e8846 --- /dev/null +++ b/app/Contribution/Documents/GallierDocument.php @@ -0,0 +1,142 @@ +> $members + */ + public function __construct( + public string $dateFrom, + public string $dateUntil, + public string $zipLocation, + public ?Country $country, + public Collection $members, + public ?string $filename = '', + public string $type = 'F', + ) { + } + + public function dateRange(): string + { + return Carbon::parse($this->dateFrom)->format('d.m.Y') + . ' - ' + . Carbon::parse($this->dateUntil)->format('d.m.Y'); + } + + /** + * {@inheritdoc} + */ + public static function fromRequest(array $request): self + { + return new self( + dateFrom: $request['dateFrom'], + dateUntil: $request['dateUntil'], + zipLocation: $request['zipLocation'], + country: Country::where('id', $request['country'])->firstOrFail(), + members: MemberData::fromModels($request['members'])->chunk(14), + ); + } + + /** + * {@inheritdoc} + */ + public static function fromApiRequest(array $request): self + { + return new self( + dateFrom: $request['dateFrom'], + dateUntil: $request['dateUntil'], + zipLocation: $request['zipLocation'], + country: Country::where('id', $request['country'])->firstOrFail(), + members: MemberData::fromApi($request['member_data'])->chunk(14), + ); + } + + public function countryName(): string + { + return $this->country->name; + } + + public function memberShort(MemberData $member): string + { + return $member->isLeader ? 'L' : ''; + } + + public function memberName(MemberData $member): string + { + return $member->separatedName(); + } + + public function memberAddress(MemberData $member): string + { + return $member->fullAddress(); + } + + public function memberGender(MemberData $member): string + { + if (!$member->gender) { + return ''; + } + + return strtolower(substr($member->gender->name, 0, 1)); + } + + public function memberAge(MemberData $member): string + { + return $member->age(); + } + + public function basename(): string + { + return 'zuschuesse-gallier'; + } + + public function view(): string + { + return 'tex.contribution.gallier'; + } + + public function template(): Template + { + return Template::make('tex.templates.contribution'); + } + + public function setFilename(string $filename): static + { + $this->filename = $filename; + + return $this; + } + + public function getEngine(): Engine + { + return Engine::PDFLATEX; + } + + public static function getName(): string + { + return 'Für RdP NRW erstellen'; + } + + /** + * @return array + */ + public static function rules(): array + { + return [ + 'dateFrom' => 'required|string|date_format:Y-m-d', + 'dateUntil' => 'required|string|date_format:Y-m-d', + 'country' => 'required|integer|exists:countries,id', + 'zipLocation' => 'required|string', + 'eventName' => 'required|string', + ]; + } +} diff --git a/resources/views/tex/contribution/gallier.tex b/resources/views/tex/contribution/gallier.tex new file mode 100644 index 00000000..789413e8 --- /dev/null +++ b/resources/views/tex/contribution/gallier.tex @@ -0,0 +1,38 @@ +\documentclass[a4paper,landscape]{article} + +\usepackage[landscape,top=0cm,left=0cm,bottom=0cm,right=0cm]{geometry} +\usepackage{tikz} +\usepackage{background} +\usepackage{blindtext} +\usetikzlibrary{matrix, shapes.misc, calc} + +\pagestyle{empty} +\setlength{\parindent}{0cm} +\backgroundsetup{scale = 1, angle = 0, opacity = 1, color=black, contents = {\includegraphics[width = \paperwidth, height = \paperheight] {rdp-nrw.pdf}}} + +\begin{document} +\noindent \sffamily + +@foreach($members as $chunk) +\begin{tikzpicture}[remember picture,overlay,yscale=-1] + \node[anchor=base west] at (38mm,41.62mm) {\bfseries{\large{<<>>}}}; +\node[anchor=base west] at (135.2mm,41.62mm) {\bfseries{\large{<<>>}}}; + +\node[thick, cross out,draw=black,text width=2.4mm, text height=2.4mm, inner sep=0mm] at (17.76mm,47.10mm) {}; + +@foreach($chunk as $i => $member) + \node[anchor=base, text width=7.75mm, align=center] at ($(16.35mm, 76.6mm + 7mm * <<<$i % 17>>>)$) {<<<$i+1>>>}; + \node[anchor=base, text width=18mm, align=center] at ($(32.55mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberShort($member)>>>}; + \node[anchor=base, text width=70mm, align=center] at ($(80.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberName($member)>>>}; + \node[anchor=base, text width=118mm, align=center] at ($(178.25mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberAddress($member)>>>}; + \node[anchor=base, text width=16mm, align=center] at ($(249.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberGender($member)>>>}; + \node[anchor=base, text width=16mm, align=center] at ($(269.50mm, 76.6mm + 7mm * <<<$i%17>>>)$) {<<<$memberAge($member)>>>}; +@endforeach + +\end{tikzpicture} + +\pagebreak + +@endforeach +\end{document} + diff --git a/resources/views/tex/templates/contribution/gallier.pdf b/resources/views/tex/templates/contribution/gallier.pdf new file mode 100644 index 00000000..c14abfdc Binary files /dev/null and b/resources/views/tex/templates/contribution/gallier.pdf differ diff --git a/tests/Feature/Contribution/StoreTest.php b/tests/Feature/Contribution/StoreTest.php index e2fd0808..9b139d15 100644 --- a/tests/Feature/Contribution/StoreTest.php +++ b/tests/Feature/Contribution/StoreTest.php @@ -14,199 +14,179 @@ use Illuminate\Foundation\Testing\DatabaseTransactions; use Laravel\Passport\Client; use Laravel\Passport\Passport; use PHPUnit\Framework\Attributes\DataProvider; +use Spatie\LaravelData\Data; use Tests\RequestFactories\ContributionMemberApiRequestFactory; use Tests\RequestFactories\ContributionRequestFactory; use Tests\TestCase; use Zoomyboy\Tex\Tex; -class StoreTest extends TestCase -{ - use DatabaseTransactions; +uses(DatabaseTransactions::class); - /** - * @testWith ["App\\Contribution\\Documents\\CitySolingenDocument", ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]] - * ["App\\Contribution\\Documents\\RdpNrwDocument", ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]] - * ["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]] - * ["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]] - * ["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]] - * - * @param array $bodyChecks - */ - public function testItCompilesContributionDocumentsViaRequest(string $type, array $bodyChecks): void - { - $this->withoutExceptionHandling(); - Tex::spy(); - $this->login()->loginNami(); - $member1 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']); - $member2 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']); - - $response = $this->call('GET', '/contribution-generate', [ - 'payload' => ContributionRequestFactory::new()->type($type)->state([ - 'dateFrom' => '1991-06-15', - 'dateUntil' => '1991-06-16', - 'eventName' => 'Super tolles Lager', - 'members' => [$member1->id, $member2->id], - 'type' => $type, - 'zipLocation' => '42777 SG', - ])->toBase64(), - ]); - - $response->assertSessionDoesntHaveErrors(); - $response->assertOk(); - Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks)); - } - - public function testItCompilesGroupNameInSolingenDocument(): void - { - $this->withoutExceptionHandling()->login()->loginNami(); - Tex::spy(); - InvoiceSettings::fake(['from_long' => 'Stamm BiPi']); - - $this->call('GET', '/contribution-generate', [ - 'payload' => ContributionRequestFactory::new()->type(CitySolingenDocument::class)->toBase64(), - ]); - - Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Stamm BiPi'])); - } - - public function testItCompilesContributionDocumentsViaApi(): void - { - $this->withoutExceptionHandling(); - Tex::spy(); - Gender::factory()->female()->create(); - Gender::factory()->male()->create(); - Passport::actingAsClient(Client::factory()->create(), ['contribution-generate']); - $country = Country::factory()->create(); - Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']); - Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']); - - $response = $this->postJson('/api/contribution-generate', [ - 'country' => $country->id, - 'dateFrom' => '1991-06-15', - 'dateUntil' => '1991-06-16', - 'eventName' => 'Super tolles Lager', - 'type' => CitySolingenDocument::class, - 'zipLocation' => '42777 SG', - 'member_data' => [ - ContributionMemberApiRequestFactory::new()->create(), - ContributionMemberApiRequestFactory::new()->create(), - ], - ]); - - $response->assertSessionDoesntHaveErrors(); - $response->assertOk(); - Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Super'])); - } - - /** - * @testWith [""] - * ["aaaa"] - * ["YWFhCg=="] - */ - public function testInputShouldBeBase64EncodedJson(string $payload): void - { - $this->login()->loginNami(); - - $this->call('GET', '/contribution-generate', ['payload' => $payload])->assertSessionHasErrors('payload'); - } - - /** - * @param array $input - * @param class-string $documentClass - */ - #[DataProvider('validationDataProvider')] - public function testItValidatesInput(array $input, string $documentClass, string $errorField): void - { - $this->login()->loginNami(); - Country::factory()->create(); - Member::factory()->defaults()->create(); - - $this->postJson('/contribution-validate', ContributionRequestFactory::new()->type($documentClass)->state($input)->create()) - ->assertJsonValidationErrors($errorField); - } - - /** - * @param array $input - * @param class-string $documentClass - */ - #[DataProvider('validationDataProvider')] - public function testItValidatesInputBeforeGeneration(array $input, string $documentClass, string $errorField): void - { - $this->login()->loginNami(); - Country::factory()->create(); - Member::factory()->defaults()->create(); - - $this->call('GET', '/contribution-generate', [ - 'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(), - ])->assertSessionHasErrors($errorField); - } - - public static function validationDataProvider(): Generator - { - yield [ +dataset('validation', function () { + return [ + [ ['type' => 'aaa'], CitySolingenDocument::class, 'type', - ]; - yield [ + ], + [ ['type' => ''], CitySolingenDocument::class, 'type', - ]; - yield [ + ], + [ ['dateFrom' => ''], CitySolingenDocument::class, 'dateFrom', - ]; - yield [ + ], + [ ['dateFrom' => '2022-01'], CitySolingenDocument::class, 'dateFrom', - ]; - yield [ + ], + [ ['dateUntil' => ''], CitySolingenDocument::class, 'dateUntil', - ]; - yield [ + ], + [ ['dateUntil' => '2022-01'], CitySolingenDocument::class, 'dateUntil', - ]; - yield [ + ], + [ ['country' => -1], RdpNrwDocument::class, 'country', - ]; - yield [ + ], + [ ['country' => 'AAAA'], RdpNrwDocument::class, 'country', - ]; - yield [ + ], + [ ['members' => 'A'], RdpNrwDocument::class, 'members', - ]; - yield [ + ], + [ ['members' => [99999]], RdpNrwDocument::class, 'members.0', - ]; - yield [ + ], + [ ['members' => ['lalala']], RdpNrwDocument::class, 'members.0', - ]; - yield [ + ], + [ ['eventName' => ''], CitySolingenDocument::class, 'eventName', - ]; - yield [ + ], + [ ['zipLocation' => ''], CitySolingenDocument::class, 'zipLocation', - ]; - } -} + ], + ]; +}); + +it('compiles documents via api', function (string $type, array $bodyChecks) { + $this->withoutExceptionHandling(); + Tex::spy(); + $this->login()->loginNami(); + $member1 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']); + $member2 = Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']); + + $response = $this->call('GET', '/contribution-generate', [ + 'payload' => ContributionRequestFactory::new()->type($type)->state([ + 'dateFrom' => '1991-06-15', + 'dateUntil' => '1991-06-16', + 'eventName' => 'Super tolles Lager', + 'members' => [$member1->id, $member2->id], + 'type' => $type, + 'zipLocation' => '42777 SG', + ])->toBase64(), + ]); + + $response->assertSessionDoesntHaveErrors(); + $response->assertOk(); + Tex::assertCompiled($type, fn ($document) => $document->hasAllContent($bodyChecks)); +})->with([ + ["App\\Contribution\\Documents\\CitySolingenDocument", ["Super tolles Lager", "Max Muster", "Jane Muster", "15.06.1991"]], + ["App\\Contribution\\Documents\\RdpNrwDocument", ["Muster, Max", "Muster, Jane", "15.06.1991", "42777 SG"]], + ["App\\Contribution\\Documents\\CityRemscheidDocument", ["Max", "Muster", "Jane"]], + ["App\\Contribution\\Documents\\CityFrankfurtMainDocument", ["Max", "Muster", "Jane"]], + ["App\\Contribution\\Documents\\BdkjHesse", ["Max", "Muster", "Jane"]], + ["App\\Contribution\\Documents\\GallierDocument", ["Max", "Muster", "Jane", "42777 SG", "15.06.1991", "16.06.1991"]], +]); + +it('testItCompilesGroupNameInSolingenDocument', function () { + $this->withoutExceptionHandling()->login()->loginNami(); + Tex::spy(); + InvoiceSettings::fake(['from_long' => 'Stamm BiPi']); + + $this->call('GET', '/contribution-generate', [ + 'payload' => ContributionRequestFactory::new()->type(CitySolingenDocument::class)->toBase64(), + ]); + + Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Stamm BiPi'])); +}); + +it('testItCompilesContributionDocumentsViaApi', function () { + $this->withoutExceptionHandling(); + Tex::spy(); + Gender::factory()->female()->create(); + Gender::factory()->male()->create(); + Passport::actingAsClient(Client::factory()->create(), ['contribution-generate']); + $country = Country::factory()->create(); + Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Max', 'lastname' => 'Muster']); + Member::factory()->defaults()->create(['address' => 'Maxstr 44', 'zip' => '42719', 'firstname' => 'Jane', 'lastname' => 'Muster']); + + $response = $this->postJson('/api/contribution-generate', [ + 'country' => $country->id, + 'dateFrom' => '1991-06-15', + 'dateUntil' => '1991-06-16', + 'eventName' => 'Super tolles Lager', + 'type' => CitySolingenDocument::class, + 'zipLocation' => '42777 SG', + 'member_data' => [ + ContributionMemberApiRequestFactory::new()->create(), + ContributionMemberApiRequestFactory::new()->create(), + ], + ]); + + $response->assertSessionDoesntHaveErrors(); + $response->assertOk(); + Tex::assertCompiled(CitySolingenDocument::class, fn ($document) => $document->hasAllContent(['Super'])); +}); + +it('testInputShouldBeBase64EncodedJson', function (string $payload) { + $this->login()->loginNami(); + + $this->call('GET', '/contribution-generate', ['payload' => $payload])->assertSessionHasErrors('payload'); +})->with([ + [""], + ["aaaa"], + ["YWFhCg=="], +]); + +it('testItValidatesInput', function (array $input, string $documentClass, string $errorField) { + $this->login()->loginNami(); + Country::factory()->create(); + Member::factory()->defaults()->create(); + + $this->postJson('/contribution-validate', ContributionRequestFactory::new()->type($documentClass)->state($input)->create()) + ->assertJsonValidationErrors($errorField); +})->with('validation'); + +it('testItValidatesInputBeforeGeneration', function (array $input, string $documentClass, string $errorField) { + $this->login()->loginNami(); + Country::factory()->create(); + Member::factory()->defaults()->create(); + + $this->call('GET', '/contribution-generate', [ + 'payload' => ContributionRequestFactory::new()->type($documentClass)->state($input)->toBase64(), + ])->assertSessionHasErrors($errorField); +})->with('validation');