193 lines
7.4 KiB
PHP
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));
|
|
}
|
|
}
|