{{-- The body of an invoice, for TCPDF's writeHTML(). Tables and inline styles, and a narrower dialect than even an email: TCPDF understands a small subset of HTML and almost no CSS. No flexbox, no grid, no shorthand borders on table cells, no percentage padding. What works is a table with widths, and text-align. Every value comes from the frozen snapshot ($s). Nothing here reads a setting or a model — that is the whole reason no PDF has to be stored. --}} @php use App\Services\Billing\InvoiceMath; $issuer = $s['issuer'] ?? []; $customer = $s['customer'] ?? []; $meta = $s['meta'] ?? []; $lines = $s['lines'] ?? []; $totals = $s['totals'] ?? []; @endphp {{-- Sender line above the address, small — the line that shows through a window envelope and tells the postman where to return it. --}}
{{ collect([$issuer['name'] ?? null, $issuer['address'] ?? null, trim(($issuer['postcode'] ?? '').' '.($issuer['city'] ?? '')), $issuer['country'] ?? null])->filter()->join(' | ') }}
{{-- No leading whitespace inside these cells. TCPDF prints it: every indented line came out indented, and only the last one — which had none — sat flush against the margin. --}} {{-- Address lines where the record keeps one free-text field, structured fields where it keeps them apart. Guessing which free-text line is the postcode would put it where the street belongs, on a document nobody can correct afterwards. --}}
{{ $customer['name'] ?? '' }}@if (! empty($customer['address_lines']))@foreach ($customer['address_lines'] as $addressLine)
{{ $addressLine }}@endforeach@else
{{ $customer['address'] ?? '' }}
{{ trim(($customer['postcode'] ?? '').' '.($customer['city'] ?? '')) }}
{{ $customer['country'] ?? '' }}@endif@if (! empty($customer['vat_id']))
{{ __('invoice.customer_vat') }}: {{ $customer['vat_id'] }}@endif
{{-- The data block, right, level with the address. Its own table because TCPDF has no way to float one beside the other. --}} @if (! empty($meta['customer_number'])) @endif @if (! empty($meta['due_on'])) @endif
{{ __('invoice.date') }} {{ $meta['issued_on'] ?? '' }}
{{ __('invoice.customer_number') }} {{ $meta['customer_number'] }}
{{ __('invoice.number') }} {{ $meta['number'] ?? '' }}
{{ __('invoice.due') }} {{ $meta['due_on'] }}
{{-- One block, not a heading with a paragraph beneath it: TCPDF gives an h1 a margin of its own and the two drifted a centimetre apart. --}}
{{ $meta['title'] ?? __('invoice.title') }}
{{ $meta['number'] ?? '' }}
@if (! empty($meta['salutation']))

{{ $meta['salutation'] }}

@endif @if (! empty($meta['intro']))

{{ $meta['intro'] }}

@endif {{-- Quantity, net unit price, net total AND gross total per line: the invoice goes to businesses and to consumers, and each reads a different column first. --}} {{-- A unit price, not a line total. Nobody adds up unit prices, so this column cannot contradict the sum — which a full-price gross TOTAL column would, by 315,60 against 288,60. --}} @foreach ($lines as $index => $line) @php // Full price throughout. The discount is collected and shown once, // in euros, beneath the subtotal — a line printed already-reduced // leaves the reader guessing whether it is reduced. $rate = (int) ($totals['rate_basis_points'] ?? 2000); $net = InvoiceMath::lineNet((int) $line['unit_net_cents'], (int) $line['quantity_milli']); $unitGross = (int) $line['unit_net_cents'] + InvoiceMath::tax((int) $line['unit_net_cents'], $rate); @endphp @endforeach
{{ __('invoice.col_pos') }} {{ __('invoice.col_description') }} {{ __('invoice.col_quantity') }} {{ __('invoice.col_unit_net') }}{{ __('invoice.col_unit_gross') }} {{ __('invoice.col_total_net') }}
{{ $index + 1 }} {{ $line['description'] ?? '' }}@foreach (($line['details'] ?? []) as $detail)
{{ $detail }}@endforeach
{{ InvoiceMath::quantity((int) $line['quantity_milli']) }}@if (! empty($line['unit'])) {{ $line['unit'] }}@endif {{ InvoiceMath::money((int) $line['unit_net_cents']) }} {{ InvoiceMath::money($unitGross) }} {{ InvoiceMath::money($net) }}
{{-- The subtotal is what the "Gesamt netto" column adds up to. Without it the discount below has nothing to be a discount OF, which is exactly what made the previous version unreadable. --}} @if (! empty($totals['adjustment'])) @endif
{{ __('invoice.subtotal') }} {{ InvoiceMath::money((int) ($totals['subtotal'] ?? 0)) }}
{{ $meta['adjustment_label'] ?? __('invoice.adjustment') }} {{ InvoiceMath::money((int) $totals['adjustment']) }}
{{ __('invoice.net') }} {{ InvoiceMath::money((int) ($totals['net'] ?? 0)) }}
{{ __('invoice.tax', ['rate' => rtrim(rtrim(number_format(($totals['rate_basis_points'] ?? 2000) / 100, 2, ',', '.'), '0'), ',')]) }} {{ InvoiceMath::money((int) ($totals['tax'] ?? 0)) }}
{{ __('invoice.gross') }} {{ InvoiceMath::money((int) ($totals['gross'] ?? 0)) }} {{ $meta['currency'] ?? 'EUR' }}
@if (! empty($meta['payment_terms']))

{{ __('invoice.payment_title') }}

{{ $meta['payment_terms'] }}

@endif @if (! empty($meta['closing']))

{{ $meta['closing'] }}

@endif