presseportale/app/Services/Billing/LegacyInvoicePdfRenderer.php
Kevin Adametz 5b8bdf4182
Some checks are pending
linter / quality (push) Waiting to run
tests / ci (push) Waiting to run
12-05-2026 Frontend dev
2026-05-12 18:32:33 +02:00

193 lines
7.4 KiB
PHP

<?php
namespace App\Services\Billing;
use App\Models\LegacyInvoice;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;
class LegacyInvoicePdfRenderer
{
public function inlineResponse(LegacyInvoice $invoice): Response
{
$pdf = $this->render($invoice);
return response($pdf, Response::HTTP_OK, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="'.$this->filename($invoice).'"',
'Cache-Control' => 'private, max-age=0, must-revalidate',
]);
}
public function downloadResponse(LegacyInvoice $invoice): Response
{
$pdf = $this->render($invoice);
return response()->streamDownload(
static function () use ($pdf): void {
echo $pdf;
},
$this->filename($invoice),
[
'Content-Type' => 'application/pdf',
'Cache-Control' => 'private, max-age=0, must-revalidate',
],
);
}
public function render(LegacyInvoice $invoice): string
{
$lines = $this->lines($invoice);
$content = "BT\n/F1 11 Tf\n50 790 Td\n14 TL\n";
foreach ($lines as $line) {
$content .= '('.$this->escapePdfText($line).") Tj\nT*\n";
}
$content .= "ET\n";
return $this->buildPdf($content);
}
public function filename(LegacyInvoice $invoice): string
{
$number = filled($invoice->number) ? (string) $invoice->number : (string) $invoice->legacy_id;
$number = preg_replace('/[^A-Za-z0-9._-]/', '-', Str::ascii($number)) ?: (string) $invoice->id;
$portal = preg_replace('/[^A-Za-z0-9._-]/', '-', Str::ascii($invoice->legacy_portal->label()));
return "{$portal}-RNr-{$number}.pdf";
}
/**
* @return list<string>
*/
private function lines(LegacyInvoice $invoice): array
{
$payload = $invoice->pdf_payload ?? [];
$billingAddress = data_get($payload, 'billing_address', []);
$invoiceData = data_get($payload, 'invoice', $invoice->raw_snapshot ?? []);
$invoice->loadMissing('user.profile');
$isNetto = (bool) data_get($invoiceData, 'is_netto', false);
$taxPercent = $this->taxPercent($invoice);
$amount = $invoice->total_cents / 100;
$netAmount = $isNetto ? $amount : $amount / (1 + ($taxPercent / 100));
$taxAmount = $isNetto ? 0 : $amount - $netAmount;
$servicePeriodBegin = $this->formatLegacyDate(data_get($invoiceData, 'service_period_begin_date'));
$servicePeriodEnd = $this->formatLegacyDate(data_get($invoiceData, 'service_period_end_date'));
$serviceName = data_get($payload, 'payment_option_translation.name')
?? data_get($payload, 'payment_option.article_number')
?? 'Legacy-Leistung';
return array_values(array_filter([
$invoice->legacy_portal->label(),
'adametz.media, Kevin Adametz, In der Lake 4, 33739 Bielefeld',
'www.businessportal24.com',
str_repeat('-', 68),
'',
'Legacy-Rechnung',
'Rechnungsdatum: '.$invoice->invoice_date?->format('d.m.Y'),
'',
'Rechnungsadresse',
data_get($billingAddress, 'name'),
data_get($billingAddress, 'title'),
data_get($billingAddress, 'address'),
trim((string) data_get($billingAddress, 'postal_code').' '.(string) data_get($billingAddress, 'city')),
data_get($billingAddress, 'country_name'),
$invoice->user?->profile?->tax_id_number ? 'UID-Nr.: '.$invoice->user->profile->tax_id_number : null,
'',
'Leistung: '.$serviceName.' auf '.$invoice->legacy_portal->label(),
$servicePeriodBegin === $servicePeriodEnd
? 'Leistungsdatum: '.$servicePeriodBegin
: 'Leistungszeitraum: '.$servicePeriodBegin.' - '.$servicePeriodEnd,
'Rechnungsnummer: '.($invoice->number ?? '#'.$invoice->legacy_id),
'',
str_repeat('-', 68),
'Rechnungsstellung: '.$invoice->invoice_date?->format('d.m.Y'),
str_repeat('-', 68),
'Netto: '.$this->formatEuro($netAmount),
$isNetto ? null : 'MwSt. '.$taxPercent.'%: '.$this->formatEuro($taxAmount),
str_repeat('-', 68),
'Betrag: '.$this->formatEuro($amount),
str_repeat('-', 68),
'',
'Zahlart: '.($invoice->payment_method ?? 'n/a'),
'Status: '.($invoice->status ?? 'unknown'),
$invoice->paid_at ? 'Bezahlt am: '.$invoice->paid_at->format('d.m.Y') : 'Faellig am: '.$invoice->due_date?->format('d.m.Y'),
'',
'Bitte ueberweisen Sie den Rechnungsbetrag unter Angabe der Rechnungsnummer '.($invoice->number ?? '#'.$invoice->legacy_id).'.',
'Bankverbindung: Sparkasse Bielefeld, IBAN DE96 4805 0161 0065 0356 02, BIC SPBIDE3BXXX',
$isNetto ? 'Reverse Charge: Steuerschuldnerschaft des Leistungsempfaengers.' : null,
'',
str_repeat('-', 68),
'adametz.media | Tel: +49 5206 7076721 | Mail: info@businessportal24.com',
'Steuernummer: 349 / 5001 / 4350 | USt-ID: DE298729654',
], fn (mixed $line): bool => $line !== null && $line !== ''));
}
private function taxPercent(LegacyInvoice $invoice): int
{
$invoiceDate = $invoice->invoice_date;
if ($invoiceDate && $invoiceDate->betweenIncluded(Carbon::parse('2020-07-01'), Carbon::parse('2020-12-31'))) {
return 16;
}
return 19;
}
private function formatLegacyDate(mixed $value): string
{
if (blank($value) || $value === '0000-00-00' || $value === '0000-00-00 00:00:00') {
return 'n/a';
}
return Carbon::parse((string) $value)->format('d.m.Y');
}
private function formatEuro(float $amount): string
{
return number_format($amount, 2, ',', '.').' EUR';
}
private function buildPdf(string $content): string
{
$objects = [
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>\nendobj\n",
"4 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n",
"5 0 obj\n<< /Length ".strlen($content)." >>\nstream\n{$content}endstream\nendobj\n",
];
$pdf = "%PDF-1.4\n";
$offsets = [0];
foreach ($objects as $object) {
$offsets[] = strlen($pdf);
$pdf .= $object;
}
$xrefOffset = strlen($pdf);
$pdf .= "xref\n0 ".(count($objects) + 1)."\n";
$pdf .= "0000000000 65535 f \n";
foreach (array_slice($offsets, 1) as $offset) {
$pdf .= sprintf("%010d 00000 n \n", $offset);
}
$pdf .= "trailer\n<< /Size ".(count($objects) + 1)." /Root 1 0 R >>\n";
$pdf .= "startxref\n{$xrefOffset}\n%%EOF\n";
return $pdf;
}
private function escapePdfText(string $text): string
{
$encoded = iconv('UTF-8', 'Windows-1252//TRANSLIT//IGNORE', $text);
return str_replace(['\\', '(', ')'], ['\\\\', '\(', '\)'], $encoded ?: Str::ascii($text));
}
}