Add DisplayPdfAction for invoices
This commit is contained in:
parent
551c658fa3
commit
b0534279b6
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 ------------------------------
|
||||
|
|
|
@ -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']));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue