From b0534279b6afa737ac4be0a56f2f426a74986225 Mon Sep 17 00:00:00 2001 From: Philipp Lang Date: Sun, 17 Dec 2023 22:33:29 +0100 Subject: [PATCH] Add DisplayPdfAction for invoices --- app/Invoice/Actions/DisplayPdfAction.php | 21 +++++++ app/Invoice/BillDocument.php | 2 +- .../{Invoice.php => InvoiceDocument.php} | 57 ++++++------------- app/Payment/Actions/DisplayPdfAction.php | 26 --------- .../Invoice/Models/InvoiceFactory.php | 3 +- ...023_12_12_015320_create_invoices_table.php | 1 + resources/views/tex/bill.tex | 4 +- routes/web.php | 10 +--- tests/Feature/Invoice/ShowPdfTest.php | 35 ++++++++++++ tests/Feature/Payment/PaymentPdfTest.php | 52 ----------------- tests/TestCase.php | 32 +++++++---- 11 files changed, 104 insertions(+), 139 deletions(-) create mode 100644 app/Invoice/Actions/DisplayPdfAction.php rename app/Invoice/{Invoice.php => InvoiceDocument.php} (57%) delete mode 100644 app/Payment/Actions/DisplayPdfAction.php create mode 100644 tests/Feature/Invoice/ShowPdfTest.php delete mode 100644 tests/Feature/Payment/PaymentPdfTest.php diff --git a/app/Invoice/Actions/DisplayPdfAction.php b/app/Invoice/Actions/DisplayPdfAction.php new file mode 100644 index 00000000..76aac191 --- /dev/null +++ b/app/Invoice/Actions/DisplayPdfAction.php @@ -0,0 +1,21 @@ + $positions */ public function __construct( - public string $familyName, - public string $singleName, - public string $address, - public string $zip, - public string $location, + public string $toName, + public string $toAddress, + public string $toZip, + public string $toLocation, + public string $greeting, public array $positions, public string $usage, - public ?string $email, ) { $this->until = now()->addWeeks(2)->format('d.m.Y'); - $this->filename = Str::slug("{$this->getSubject()} für {$familyName}"); + $this->filename = Str::slug("{$this->getSubject()} für {$toName}"); } - /** - * @param Collection<(int|string), Member> $members - */ - public static function fromMembers(Collection $members): self + public static function fromInvoice(Invoice $invoice): self { return static::withoutMagicalCreationFrom([ - 'familyName' => $members->first()->lastname, - 'singleName' => $members->first()->lastname, - 'address' => $members->first()->address, - 'zip' => $members->first()->zip, - 'location' => $members->first()->location, - 'email' => $members->first()->email_parents ?: $members->first()->email, - 'positions' => static::renderPositions($members), - 'usage' => "Mitgliedsbeitrag für {$members->first()->lastname}", + 'toName' => $invoice->to['name'], + 'toAddress' => $invoice->to['address'], + 'toZip' => $invoice->to['zip'], + 'toLocation' => $invoice->to['location'], + 'greeting' => $invoice->greeting, + 'positions' => static::renderPositions($invoice), + 'usage' => $invoice->usage, ]); } @@ -116,26 +110,11 @@ abstract class Invoice extends Document } /** - * @param Collection<(int|string), Member> $members - * * @return array */ - public static function renderPositions(Collection $members): array + public static function renderPositions(Invoice $invoice): array { - /** @var array */ - $result = []; - - foreach ($members->pluck('payments')->flatten(1) as $payment) { - if ($payment->subscription->split) { - foreach ($payment->subscription->children as $child) { - $result["{$payment->subscription->name} ({$child->name}) {$payment->nr} für {$payment->member->firstname} {$payment->member->lastname}"] = static::number($child->amount); - } - } else { - $result["{$payment->subscription->name} {$payment->nr} für {$payment->member->firstname} {$payment->member->lastname}"] = static::number($payment->subscription->getAmount()); - } - } - - return $result; + return $invoice->positions->mapWithKeys(fn ($position) => [$position->description => static::number($position->price)])->toArray(); } public static function number(int $number): string diff --git a/app/Payment/Actions/DisplayPdfAction.php b/app/Payment/Actions/DisplayPdfAction.php deleted file mode 100644 index 5ee5bfc9..00000000 --- a/app/Payment/Actions/DisplayPdfAction.php +++ /dev/null @@ -1,26 +0,0 @@ -invoice_data) { - return response()->noContent(); - } - - $invoice = BillDocument::from($payment->invoice_data); - - return Tex::compile($invoice); - } -} diff --git a/database/factories/Invoice/Models/InvoiceFactory.php b/database/factories/Invoice/Models/InvoiceFactory.php index 553b630f..63b07334 100644 --- a/database/factories/Invoice/Models/InvoiceFactory.php +++ b/database/factories/Invoice/Models/InvoiceFactory.php @@ -27,7 +27,8 @@ class InvoiceFactory extends Factory 'greeting' => $this->faker->words(4, true), 'to' => ReceiverRequestFactory::new()->create(), 'status' => InvoiceStatus::NEW->value, - 'via' => BillKind::POST->value + 'via' => BillKind::POST->value, + 'usage' => $this->faker->words(4, true), ]; } diff --git a/database/migrations/2023_12_12_015320_create_invoices_table.php b/database/migrations/2023_12_12_015320_create_invoices_table.php index 52814d2c..5dce356c 100644 --- a/database/migrations/2023_12_12_015320_create_invoices_table.php +++ b/database/migrations/2023_12_12_015320_create_invoices_table.php @@ -20,6 +20,7 @@ return new class extends Migration $table->string('status'); $table->date('sent_at')->nullable(); $table->string('via'); + $table->string('usage'); $table->timestamps(); }); diff --git a/resources/views/tex/bill.tex b/resources/views/tex/bill.tex index a4a38fea..6c414b35 100644 --- a/resources/views/tex/bill.tex +++ b/resources/views/tex/bill.tex @@ -11,10 +11,10 @@ \setkomavar{fromlogo}{\includegraphics[width=2cm]{logo.png}} % stammeslogo \begin{document} -\begin{letter}{Familie <<< $familyName >>>\\<<< $address >>>\\<<< $zip >>> <<< $location >>>} +\begin{letter}{<<< $toName >>>\\<<< $toAddress >>>\\<<< $toZip >>> <<< $toLocation >>>} \sffamily \gdef\TotalHT{0} - \opening{Liebe Familie <<< $familyName >>>,} + \opening{<<< $greeting >>>,} Hiermit stellen wir Ihnen den aktuellen Mitgliedsbeitrag für den \usekomavar*{fromname} und die DPSG in Rechnung. Dieser setzt sich wie folgt zusammen: diff --git a/routes/web.php b/routes/web.php index 0becc6a5..145a621c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -25,6 +25,7 @@ use App\Initialize\Actions\InitializeFormAction; use App\Initialize\Actions\NamiGetSearchLayerAction; use App\Initialize\Actions\NamiLoginCheckAction; use App\Initialize\Actions\NamiSearchAction; +use App\Invoice\Actions\DisplayPdfAction; use App\Invoice\Actions\InvoiceDestroyAction; use App\Invoice\Actions\InvoiceIndexAction; use App\Invoice\Actions\InvoiceUpdateAction; @@ -50,11 +51,6 @@ use App\Membership\Actions\MembershipDestroyAction; use App\Membership\Actions\MembershipStoreAction; use App\Membership\Actions\MembershipUpdateAction; use App\Membership\Actions\StoreForGroupAction; -use App\Payment\Actions\DisplayPdfAction; -use App\Payment\Actions\IndexAction as PaymentIndexAction; -use App\Payment\Actions\PaymentDestroyAction; -use App\Payment\Actions\PaymentStoreAction; -use App\Payment\Actions\PaymentUpdateAction; use App\Payment\SendpaymentController; use App\Payment\SubscriptionController; @@ -107,9 +103,6 @@ Route::group(['middleware' => 'auth:web'], function (): void { // ----------------------------------- group ----------------------------------- Route::get('/group', ListAction::class)->name('group.index'); - // ---------------------------------- payment ---------------------------------- - Route::get('/payment/{payment}/pdf', DisplayPdfAction::class)->name('payment.pdf'); - // -------------------------------- allpayment --------------------------------- Route::post('/invoice/mass-store', MassStoreAction::class)->name('invoice.mass-store'); @@ -118,6 +111,7 @@ Route::group(['middleware' => 'auth:web'], function (): void { Route::post('/invoice', InvoiceStoreAction::class)->name('invoice.store'); Route::patch('/invoice/{invoice}', InvoiceUpdateAction::class)->name('invoice.update'); Route::delete('/invoice/{invoice}', InvoiceDestroyAction::class)->name('invoice.destroy'); + Route::get('/invoice/{invoice}/pdf', DisplayPdfAction::class)->name('invoice.pdf'); // ----------------------------- invoice-position ------------------------------ diff --git a/tests/Feature/Invoice/ShowPdfTest.php b/tests/Feature/Invoice/ShowPdfTest.php new file mode 100644 index 00000000..65391a63 --- /dev/null +++ b/tests/Feature/Invoice/ShowPdfTest.php @@ -0,0 +1,35 @@ +login()->loginNami(); + $invoice = Invoice::factory() + ->to(ReceiverRequestFactory::new()->name('Familie Lala')) + ->has(InvoicePosition::factory()->withMember()->description('Beitrag12'), 'positions') + ->via(BillKind::EMAIL) + ->create(); + + $this->get(route('invoice.pdf', ['invoice' => $invoice])) + ->assertOk() + ->assertPdfPageCount(1) + ->assertPdfName('rechnung-fur-familie-lala.pdf'); + + Tex::assertCompiled(BillDocument::class, fn ($document) => $document->hasAllContent(['Beitrag12', 'Familie Lala'])); + } +} diff --git a/tests/Feature/Payment/PaymentPdfTest.php b/tests/Feature/Payment/PaymentPdfTest.php deleted file mode 100644 index 5a1c085b..00000000 --- a/tests/Feature/Payment/PaymentPdfTest.php +++ /dev/null @@ -1,52 +0,0 @@ -login()->loginNami(); - $member = Member::factory() - ->defaults() - ->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', [ - new Child('a', 5400), - ])) - ->emailBillKind() - ->create(['firstname' => 'Lah', 'lastname' => 'Mom', 'email' => 'peter@example.com']); - /** @var Collection<(int|string), Member> */ - $members = collect([$member]); - app(DocumentFactory::class)->afterSingle(BillDocument::fromMembers($members), $members); - - $response = $this->get(route('payment.pdf', ['payment' => $member->payments->first()])); - $response->assertOk(); - $this->assertPdfPageCount(1, $response->getFile()); - } - - public function testItReturnsNoPdfWhenPaymentDoesntHaveInvoiceData(): void - { - $this->login()->loginNami(); - $member = Member::factory() - ->defaults() - ->has(Payment::factory()->notPaid()->nr('1997')->subscription('tollerbeitrag', [ - new Child('a', 5400), - ])) - ->emailBillKind() - ->create(['firstname' => 'Lah', 'lastname' => 'Mom', 'email' => 'peter@example.com']); - - $response = $this->get(route('payment.pdf', ['payment' => $member->payments->first()])); - $response->assertStatus(204); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index 60154033..c8632f9c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -111,16 +111,6 @@ abstract class TestCase extends BaseTestCase return $this; } - public function assertPdfPageCount(int $pageCount, File $file): void - { - $this->assertTrue(file_exists($file->getPathname())); - exec('pdfinfo ' . escapeshellarg($file->getPathname()) . ' | grep ^Pages | sed "s/Pages:\s*//"', $output, $returnVar); - - $this->assertSame(0, $returnVar, 'Failed to get Pages of PDF File ' . $file->getPathname()); - $this->assertCount(1, $output, 'Failed to parse output format of pdfinfo'); - $this->assertEquals($pageCount, $output[0]); - } - public function initInertiaTestcase(): void { TestResponse::macro('assertInertiaPath', function ($path, $value) { @@ -132,5 +122,27 @@ abstract class TestCase extends BaseTestCase $json->assertPath($path, $value); return $this; }); + + TestResponse::macro('assertPdfPageCount', function (int $count) { + /** @var TestResponse */ + $response = $this; + $file = $response->getFile(); + Assert::assertTrue(file_exists($file->getPathname())); + exec('pdfinfo ' . escapeshellarg($file->getPathname()) . ' | grep ^Pages | sed "s/Pages:\s*//"', $output, $returnVar); + + Assert::assertSame(0, $returnVar, 'Failed to get Pages of PDF File ' . $file->getPathname()); + Assert::assertCount(1, $output, 'Failed to parse output format of pdfinfo'); + Assert::assertEquals($count, $output[0]); + + return $this; + }); + + TestResponse::macro('assertPdfName', function (string $filename) { + /** @var TestResponse */ + $response = $this; + Assert::assertEquals($filename, $response->getFile()->getFilename()); + + return $this; + }); } }