96 lines
3.3 KiB
PHP
96 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Billing;
|
|
|
|
use TCPDF;
|
|
|
|
/**
|
|
* The page furniture: the logo block at the top and the footer at the bottom.
|
|
*
|
|
* A subclass because TCPDF draws headers and footers through overridden
|
|
* methods, and the footer has to repeat on every page — an invoice that runs to
|
|
* two pages must carry the company's register and VAT numbers on both, or the
|
|
* second page is not part of a valid invoice.
|
|
*
|
|
* The body is not built here. It is a Blade template rendered to HTML and
|
|
* handed to writeHTML(), because a table of line items expressed as a hundred
|
|
* Cell() calls with millimetre coordinates is a table nobody will ever adjust.
|
|
*/
|
|
final class InvoiceDocument extends TCPDF
|
|
{
|
|
/** @var array<string, mixed> */
|
|
private array $issuer = [];
|
|
|
|
private string $logoFile = '';
|
|
|
|
private string $pageLabel = 'Seite';
|
|
|
|
/** @param array<string, mixed> $issuer */
|
|
public function setIssuer(array $issuer, string $logoFile = '', string $pageLabel = 'Seite'): void
|
|
{
|
|
$this->issuer = $issuer;
|
|
$this->logoFile = $logoFile;
|
|
$this->pageLabel = $pageLabel;
|
|
}
|
|
|
|
public function Header(): void // phpcs:ignore
|
|
{
|
|
if ($this->logoFile === '' || ! is_file($this->logoFile)) {
|
|
return;
|
|
}
|
|
|
|
// Top right, height-constrained. Width 0 lets TCPDF keep the aspect
|
|
// ratio — a logo squeezed to a fixed box is worse than none.
|
|
$this->Image($this->logoFile, 140, 12, 0, 18, '', '', 'T', false, 300, 'R');
|
|
}
|
|
|
|
public function Footer(): void // phpcs:ignore
|
|
{
|
|
$this->SetY(-26);
|
|
$this->SetFont('dejavusans', '', 6.5);
|
|
$this->SetTextColor(110, 110, 122);
|
|
|
|
$columns = [
|
|
[
|
|
$this->issuer['name'] ?? '',
|
|
$this->issuer['address'] ?? '',
|
|
trim(($this->issuer['postcode'] ?? '').' '.($this->issuer['city'] ?? '')),
|
|
],
|
|
[
|
|
$this->issuer['phone'] ?? '',
|
|
$this->issuer['email'] ?? '',
|
|
$this->issuer['website'] ?? '',
|
|
],
|
|
[
|
|
$this->issuer['register_number'] ?? '',
|
|
$this->issuer['vat_id'] ?? '',
|
|
$this->issuer['register_court'] ?? '',
|
|
],
|
|
[
|
|
$this->issuer['bank_name'] ?? '',
|
|
$this->issuer['iban'] ?? '',
|
|
$this->issuer['bic'] ?? '',
|
|
],
|
|
];
|
|
|
|
// A hairline above it, the same one the totals block uses, so the page
|
|
// reads as one document rather than two halves.
|
|
$this->SetDrawColor(233, 233, 238);
|
|
$this->Line(20, $this->GetY() - 3, 190, $this->GetY() - 3);
|
|
|
|
$width = 42.5;
|
|
|
|
foreach ($columns as $index => $lines) {
|
|
$this->SetXY(20 + ($index * $width), -24);
|
|
// Empty entries are dropped rather than printed as blank lines: a
|
|
// company with no register number should not have a gap where one
|
|
// would be.
|
|
$this->MultiCell($width - 2, 3, implode("\n", array_filter($lines, fn ($l) => trim((string) $l) !== '')), 0, 'L', false, 0, '', '', true, 0, false, true, 0, 'T');
|
|
}
|
|
|
|
$this->SetXY(150, -30);
|
|
$this->SetFont('dejavusans', '', 6.5);
|
|
$this->Cell(40, 3, $this->pageLabel.' '.$this->getAliasNumPage().'/'.$this->getAliasNbPages(), 0, 0, 'R');
|
|
}
|
|
}
|