@php $owner = data_get($draft, 'owner', []); $client = data_get($draft, 'client', []); $property = data_get($draft, 'property', []); $items = is_array(data_get($draft, 'items')) ? data_get($draft, 'items') : []; $extra = is_array(data_get($draft, 'extra_lines')) ? data_get($draft, 'extra_lines') : []; $extraInvoiceHt = collect($extra) ->filter(fn ($line) => (($line['kind'] ?? 'invoice') === 'invoice')) ->sum(fn ($line) => (float) data_get($line, 'montant', 0)); $ht = collect($items)->sum(fn ($i) => (float) data_get($i, 'montant', 0)) + $extraInvoiceHt; $ttc = (float) data_get($draft, 'montantTtc', $ht); $vatAmount = $ttc - $ht; $ownerLegal = data_get($owner, 'legal', []); if (is_string($ownerLegal)) $ownerLegal = json_decode($ownerLegal, true) ?: []; $ownerBank = data_get($owner, 'bank', []); if (is_string($ownerBank)) $ownerBank = json_decode($ownerBank, true) ?: []; $ownerData = data_get($owner, 'data', []); if (is_string($ownerData)) $ownerData = json_decode($ownerData, true) ?: []; $ownerLegal = array_merge(is_array($ownerData['legal'] ?? null) ? $ownerData['legal'] : [], is_array($ownerLegal) ? $ownerLegal : []); $ownerBank = array_merge(is_array($ownerData['bank'] ?? null) ? $ownerData['bank'] : [], is_array($ownerBank) ? $ownerBank : []); $clientData = data_get($client, 'data', []); if (is_string($clientData)) $clientData = json_decode($clientData, true) ?: []; $clientBilling = data_get($client, 'billing_contact', []); if (is_string($clientBilling)) $clientBilling = json_decode($clientBilling, true) ?: []; if (!$clientBilling && is_array($clientData['billing_contact'] ?? null)) { $clientBilling = $clientData['billing_contact']; } $logo = data_get($owner, 'logo', data_get($ownerLegal, 'logo')); $logoPath = null; if ($logo) { // The image dynamic field stores a `media/img` URL, earlier values a public disk path. $path = ltrim((string) parse_url($logo, PHP_URL_PATH), '/'); $relative = preg_replace('#^storage/#', '', $path); $candidates = [$logo, storage_path('app/img/' . basename($path)), public_path($relative), storage_path('app/public/' . $relative)]; foreach ($candidates as $candidate) { if (is_file($candidate)) { $logoPath = $candidate; break; } } } // The logo is sized from the `image_dimension` meta of its dynamic field. The // tenant block takes the right side of the letterhead, so the width is capped. $logoBox = data_get($owner, 'logo_dimension') ?: \App\Models\Party::logoDimension(); $logoWidth = min((int) data_get($logoBox, 'width', 190), 430); $logoHeight = (int) data_get($logoBox, 'height', 72); $intrinsic = $logoPath ? @getimagesize($logoPath) : false; if ($intrinsic && $intrinsic[0] > 0 && $intrinsic[1] > 0) { // dompdf only honours explicit dimensions, so the picture is fitted in the box here. $scale = min($logoWidth / $intrinsic[0], $logoHeight / $intrinsic[1]); $logoWidth = (int) round($intrinsic[0] * $scale); $logoHeight = (int) round($intrinsic[1] * $scale); } // The logo is intentionally reduced by 46.7% in total, without changing its top-left anchor. $logoWidth = (int) round($logoWidth * (8 / 15)); $logoHeight = (int) round($logoHeight * (8 / 15)); // Keep the logo in place while moving the invoice identification down three more lines. // A body line is about 12px: growing the letterhead lowers the blue rule, and // with it everything laid out below, by two lines. $topHeight = ($logoPath ? max(241, $logoHeight + 101) : 241) + 2 * 12; $dueDate = data_get($draft, 'due_date', data_get($invoice->body, 'due_date')); $formatFrenchDate = function ($value) { if (!$value) return '—'; try { $d = \Carbon\Carbon::parse($value); $months = ['jan', 'fév', 'mar', 'avr', 'mai', 'juin', 'juil', 'aoû', 'sep', 'oct', 'nov', 'déc']; return $d->format('d') . '-' . $months[(int) $d->format('n') - 1] . '-' . $d->format('Y'); } catch (\Throwable $e) { return '—'; } }; // Rates are stored as decimals (0.20) by the invoice service, but older // invoices may contain a percentage (20). Keep both formats readable. $vatRateDecimal = function ($value) { $rate = (float) $value; return $rate > 1 ? $rate / 100 : $rate; }; $bankAddress = data_get($ownerBank, 'agency_address', data_get($ownerBank, 'agency.address', data_get($ownerBank, 'address', data_get($owner, 'bank_address', data_get($owner, 'bank_agency_address', ''))))); $legalForm = data_get($owner, 'legal_form', data_get($ownerLegal, 'legal_form', '')); $capital = data_get($owner, 'capital', data_get($owner, 'share_capital', data_get($ownerLegal, 'capital', data_get($ownerLegal, 'share_capital', '')))); $siren = data_get($owner, 'siren', data_get($ownerLegal, 'siren', data_get($owner, 'company_id', ''))); $rcs = data_get($owner, 'rcs', data_get($ownerLegal, 'rcs', data_get($owner, 'rcs_number', ''))); $phone = data_get($owner, 'phone', data_get($ownerLegal, 'phone', '')); $email = data_get($owner, 'email', data_get($ownerLegal, 'email', '')); $vatNum = data_get($owner, 'vat_num', data_get($ownerLegal, 'vat_num', '')); $legalForm = $legalForm ?: data_get($owner, 'legal_title', data_get($ownerLegal, 'title', '')); $siren = $siren ?: data_get($owner, 'legal_company_id', data_get($ownerLegal, 'company_id', '')); $bankAddress = $bankAddress ?: data_get($ownerBank, 'address_short', ''); // A line carries a variable number of `libelleN` fields plus an optional // formula. Mirrors descriptionLines() of the generator preview so both list // the same lines instead of the first two only. $descriptionLines = function ($item) { $item = is_array($item) ? $item : []; $keys = array_values(array_filter(array_keys($item), fn ($key) => preg_match('/^libelle\d+$/', (string) $key))); usort($keys, fn ($a, $b) => (int) substr((string) $a, 7) <=> (int) substr((string) $b, 7)); $lines = []; foreach ($keys as $key) { $value = trim((string) $item[$key]); if ($value !== '') $lines[] = $value; } if (!$lines) $lines = array_filter([trim((string) data_get($item, 'description', ''))]); if (!$lines) $lines = array_filter([trim((string) data_get($item, 'accountType', ''))]); $formula = trim((string) data_get($item, 'formule', '')); if ($formula !== '') $lines[] = $formula; return array_values(array_unique($lines)); }; @endphp
| Ventilation | Libellé | Montant H.T. | Taux | Montant T.T.C. |
|---|---|---|---|---|
| {{ data_get($item, 'accountType', data_get($item, 'accountNum', '')) }} | {!! implode(' ', array_map('e', $descriptionLines($item))) !!} |
{{ number_format($amount, 2, ',', ' ') }} € | {{ number_format($rate * 100, 1, ',', ' ') }}% | {{ number_format($amount * (1 + $rate), 2, ',', ' ') }} € |
| {{ $line['description'] ?? '' }} | ||||
| {{ data_get($line, 'accountType', data_get($line, 'accountNum', '')) }} | {!! implode(' ', array_map('e', $descriptionLines($line))) !!} |
{{ number_format($amount, 2, ',', ' ') }} € | {{ number_format($rate * 100, 1, ',', ' ') }}% | {{ number_format($amount * (1 + $rate), 2, ',', ' ') }} € |
| Total H.T. | {{ number_format((float) $ht, 2, ',', ' ') }} € |
| T.V.A. | {{ number_format((float) $vatAmount, 2, ',', ' ') }} € |
| Total T.T.C. | {{ number_format((float) $ttc, 2, ',', ' ') }} € |
Net à payer :{{ number_format((float) $ttc, 2, ',', ' ') }} €
Échéance :{{ $formatFrenchDate($dueDate) }}