120 lines
4.9 KiB
PHP
120 lines
4.9 KiB
PHP
<?php
|
||
|
||
namespace App\Services\Billing;
|
||
|
||
use App\Support\CompanyProfile;
|
||
|
||
/**
|
||
* A specimen invoice, for looking at the layout before anything is issued.
|
||
*
|
||
* It uses the real company details where they have been entered and obvious
|
||
* placeholders where they have not, so the preview shows what is still missing
|
||
* rather than a page that looks finished and is not.
|
||
*
|
||
* The customer and the lines are invented on purpose and say so. A specimen
|
||
* that looks like a real document is a specimen somebody eventually files.
|
||
*/
|
||
final class SampleInvoice
|
||
{
|
||
/** @return array<string, mixed> */
|
||
public static function snapshot(): array
|
||
{
|
||
$issuer = CompanyProfile::all();
|
||
|
||
// Every field a placeholder, not only the required ones. A specimen
|
||
// exists to show the layout, and a footer with two of its four columns
|
||
// empty shows a layout that is not there — the first version of this
|
||
// was read as a missing footer rather than as missing data.
|
||
$placeholders = [
|
||
'name' => 'Ihr Firmenwortlaut e.U.',
|
||
'address' => 'Musterstraße 1',
|
||
'postcode' => '1010',
|
||
'city' => 'Wien',
|
||
'country' => 'Österreich',
|
||
'phone' => '+43 000 000000',
|
||
'email' => 'office@ihre-domain.at',
|
||
'website' => 'www.ihre-domain.at',
|
||
'register_number' => 'FN 000000a',
|
||
'register_court' => 'Handelsgericht Wien',
|
||
'vat_id' => 'ATU00000000',
|
||
'bank_name' => 'Bank: Ihre Bank',
|
||
'iban' => 'AT00 0000 0000 0000 0000',
|
||
'bic' => 'BIC: XXXXATWW',
|
||
];
|
||
|
||
foreach ($placeholders as $field => $placeholder) {
|
||
if (trim((string) ($issuer[$field] ?? '')) === '') {
|
||
$issuer[$field] = $placeholder;
|
||
}
|
||
}
|
||
|
||
$rate = (int) round(CompanyProfile::taxRate() * 100);
|
||
|
||
$lines = [
|
||
[
|
||
'description' => 'CluPilot Cloud — Paket Start',
|
||
'details' => ['Abrechnungszeitraum 01.08.2026 – 31.08.2026', '500 GB Speicher, 4 Kerne, 8 GB RAM'],
|
||
'quantity_milli' => 1000,
|
||
'unit' => '',
|
||
'unit_net_cents' => 1900,
|
||
],
|
||
[
|
||
'description' => 'Zusatzspeicher 250 GB',
|
||
'details' => ['Add-on, monatlich'],
|
||
'quantity_milli' => 2000,
|
||
'unit' => '',
|
||
'unit_net_cents' => 500,
|
||
],
|
||
[
|
||
'description' => 'Zusätzliches Datenvolumen',
|
||
'details' => ['Add-on, monatlich'],
|
||
'quantity_milli' => 1000,
|
||
'unit' => '',
|
||
'unit_net_cents' => 900,
|
||
],
|
||
[
|
||
'description' => 'Einrichtung und Datenübernahme',
|
||
'details' => ['einmalig, 2,5 Stunden'],
|
||
'quantity_milli' => 2500,
|
||
'unit' => 'Std.',
|
||
'unit_net_cents' => 9000,
|
||
// On the line rather than under the table, so the gross column
|
||
// adds up to the total printed beneath it.
|
||
'adjustment' => ['type' => InvoiceMath::PERCENT, 'value' => -1000],
|
||
],
|
||
];
|
||
|
||
// No invoice-wide adjustment on the specimen: the discount sits on the
|
||
// line it belongs to, which is what keeps the columns adding up.
|
||
$totals = InvoiceMath::totals($lines, null, $rate) + ['rate_basis_points' => $rate];
|
||
|
||
return [
|
||
'issuer' => $issuer,
|
||
'customer' => [
|
||
'name' => 'Muster GmbH (Beispiel)',
|
||
'address' => 'Beispielgasse 12/3',
|
||
'postcode' => '1030',
|
||
'city' => 'Wien',
|
||
'country' => 'Österreich',
|
||
'vat_id' => 'ATU11111111',
|
||
],
|
||
'meta' => [
|
||
'title' => __('invoice.title'),
|
||
'number' => 'RE-'.now()->format('Y').'-0001',
|
||
'customer_number' => 'KN-0001',
|
||
'issued_on' => now()->local()->format('d.m.Y'),
|
||
'due_on' => now()->local()->addDays((int) CompanyProfile::get('payment_days', 14))->format('d.m.Y'),
|
||
'currency' => 'EUR',
|
||
'salutation' => 'Sehr geehrte Damen und Herren,',
|
||
'intro' => 'wir erlauben uns, für die im Folgenden aufgeführten Leistungen in Rechnung zu stellen.',
|
||
'payment_terms' => trim((string) CompanyProfile::get('payment_terms')) !== ''
|
||
? (string) CompanyProfile::get('payment_terms')
|
||
: 'Zahlbar ohne Abzug innerhalb von '.CompanyProfile::get('payment_days', 14).' Tagen auf das unten angeführte Konto.',
|
||
'closing' => 'Dies ist ein Muster zur Ansicht des Layouts. Es ist keine Rechnung und keine Zahlungsaufforderung.',
|
||
],
|
||
'lines' => $lines,
|
||
'totals' => $totals,
|
||
];
|
||
}
|
||
}
|