Add DisplayPdfAction for invoices

This commit is contained in:
Philipp Lang 2023-12-17 22:33:29 +01:00
parent 551c658fa3
commit b0534279b6
11 changed files with 104 additions and 139 deletions

View File

@ -0,0 +1,21 @@
<?php
namespace App\Invoice\Actions;
use App\Invoice\BillDocument;
use App\Invoice\Models\Invoice;
use App\Payment\Payment;
use Illuminate\Http\Response;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\Tex\BaseCompiler;
use Zoomyboy\Tex\Tex;
class DisplayPdfAction
{
use AsAction;
public function handle(Invoice $invoice): BaseCompiler|Response
{
return Tex::compile(BillDocument::fromInvoice($invoice));
}
}

View File

@ -5,7 +5,7 @@ namespace App\Invoice;
use App\Payment\Payment;
use Illuminate\Database\Eloquent\Relations\HasMany;
class BillDocument extends Invoice
class BillDocument extends InvoiceDocument
{
public function linkLabel(): string
{

View File

@ -2,17 +2,16 @@
namespace App\Invoice;
use App\Member\Member;
use App\Invoice\Models\Invoice;
use App\Payment\Payment;
use Exception;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Zoomyboy\Tex\Document;
use Zoomyboy\Tex\Engine;
use Zoomyboy\Tex\Template;
abstract class Invoice extends Document
abstract class InvoiceDocument extends Document
{
abstract public function getSubject(): string;
abstract public function view(): string;
@ -39,33 +38,28 @@ abstract class Invoice extends Document
* @param array<string, string> $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<string, string>
*/
public static function renderPositions(Collection $members): array
public static function renderPositions(Invoice $invoice): array
{
/** @var array<string, string> */
$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

View File

@ -1,26 +0,0 @@
<?php
namespace App\Payment\Actions;
use App\Invoice\BillDocument;
use App\Payment\Payment;
use Illuminate\Http\Response;
use Lorisleiva\Actions\Concerns\AsAction;
use Zoomyboy\Tex\BaseCompiler;
use Zoomyboy\Tex\Tex;
class DisplayPdfAction
{
use AsAction;
public function handle(Payment $payment): BaseCompiler|Response
{
if (null === $payment->invoice_data) {
return response()->noContent();
}
$invoice = BillDocument::from($payment->invoice_data);
return Tex::compile($invoice);
}
}

View File

@ -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),
];
}

View File

@ -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();
});

View File

@ -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:

View File

@ -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 ------------------------------

View File

@ -0,0 +1,35 @@
<?php
namespace Tests\Feature\Invoice;
use App\Invoice\BillDocument;
use App\Invoice\BillKind;
use App\Invoice\Models\Invoice;
use App\Invoice\Models\InvoicePosition;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
use Zoomyboy\Tex\Tex;
class ShowPdfTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsAnInvoiceAsPdf(): void
{
Tex::spy();
$this->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']));
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace Tests\Feature\Payment;
use App\Invoice\BillDocument;
use App\Invoice\DocumentFactory;
use App\Member\Member;
use App\Payment\Payment;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Collection;
use Tests\RequestFactories\Child;
use Tests\TestCase;
class PaymentPdfTest extends TestCase
{
use DatabaseTransactions;
public function testItShowsAnInvoiceAsPdf(): 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']);
/** @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);
}
}

View File

@ -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;
});
}
}