CluPilotCloud/app/Services/Billing/InvoiceDocument.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');
}
}