Quelltext des aktuellen Standes
<?php
/**
* Kalkulation als PDF (Dompdf, Landscape). Optik wie der Vertrags-Export.
* Endpreis in zwei Werten: einmalig und laufend (pro Jahr = monatlich×12 +
* jährlich). Druck-/PDF-Kontext → Hex-Farben laut STYLEGUIDE.md zulässig.
*/
$disp = ($display ?? 'net') === 'gross' ? 'gross' : 'net';
$money = fn($v) => $v === null ? '–' : $this->moneyHtml($v);
$billingLabels = $billing ?? ['once' => 'einmalig', 'monthly' => 'pro Monat', 'yearly' => 'pro Jahr'];
$rows = $rows ?? [];
$appName = $this->e($app_name ?? 'OpenNIT');
$today = date('d.m.Y H:i');
$priceLabel = $disp === 'gross' ? 'Brutto' : 'Netto';
// Summen nach Zyklus, dann zu einmalig/laufend (Jahr) verdichtet.
$byCycle = ['once' => 0.0, 'monthly' => 0.0, 'yearly' => 0.0];
foreach ($rows as $r) {
$pv = $r['price_' . $disp];
if ($pv === null) { continue; }
$byCycle[$r['billing_cycle'] ?? 'once'] += (float)$pv * (int)$r['quantity'];
}
$totalOnce = $byCycle['once'];
$totalRec = $byCycle['monthly'] * 12 + $byCycle['yearly'];
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title><?= $this->te('Kalkulation') ?></title>
<style>
@page { margin: 1.5cm 1.5cm 2cm; }
body { font-family: DejaVu Sans, Arial, sans-serif; font-size: 9pt; color: #1f2937; }
h1 { font-size: 16pt; margin: 0 0 2px; color: #1a1a2e; }
.meta { font-size: 8pt; color: #6b7280; margin-bottom: 12px; }
.price-mode { display: inline-block; font-size: 7.5pt; font-weight: bold; background: #dbeafe; color: #1e40af; border: 1px solid #bfdbfe; border-radius: 3px; padding: 1px 5px; letter-spacing: 0.04em; vertical-align: middle; margin-left: 6px; }
.summary-box { display: table; width: 100%; border-collapse: collapse; margin-bottom: 14px; }
.summary-cell { display: table-cell; background: #f0f7ff; border: 1px solid #bfd9f2; padding: 8px 14px; width: 50%; text-align: center; }
.summary-cell .label { font-size: 7.5pt; color: #6b7280; text-transform: uppercase; letter-spacing: 0.04em; }
.summary-cell .value { font-size: 15pt; font-weight: bold; color: #1a4a7a; }
.summary-cell .sub { font-size: 7.5pt; color: #6b7280; }
table.main { width: 100%; border-collapse: collapse; }
table.main th { background: #1a4a7a; color: #fff; font-size: 8pt; padding: 5px 6px; text-align: left; }
table.main th.num { text-align: right; }
table.main td { font-size: 8pt; padding: 4px 6px; border-bottom: 1px solid #e5e7eb; vertical-align: top; }
td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
tr.total-row td { background: #1a4a7a; color: #fff; font-weight: bold; font-size: 9pt; padding: 6px 6px; }
tr.subtotal td { background: #eff6ff; font-size: 8pt; border-top: 1px solid #bfd9f2; padding: 4px 6px; }
.pill-req { display: inline-block; font-size: 6.8pt; font-weight: bold; background: #eef2ff; color: #4338ca; border: 1px solid #c7d2fe; border-radius: 3px; padding: 0 4px; margin-left: 4px; vertical-align: middle; }
.empty { color: #6b7280; padding: 10px 0; }
.footer { border-top: 1px solid #e5e7eb; font-size: 7.5pt; color: #9ca3af; padding-top: 4px; margin-top: 14px; }
</style>
</head>
<body>
<h1><?= $this->te('Kalkulation') ?> <span class="price-mode"><?= $priceLabel ?></span></h1>
<div class="meta"><?= $this->t('Erstellt am') ?> <?= $today ?> • <?= $appName ?> • <?= $this->te('„Laufend" = wiederkehrende Kosten pro Jahr (monatliche ×12)') ?></div>
<table class="summary-box">
<tr>
<td class="summary-cell">
<div class="label"><?= $this->t('Einmalig') ?> (<?= $priceLabel ?>)</div>
<div class="value"><?= $money($totalOnce) ?></div>
</td>
<td class="summary-cell" style="border-left:none">
<div class="label"><?= $this->t('Laufend / Jahr') ?> (<?= $priceLabel ?>)</div>
<div class="value"><?= $money($totalRec) ?></div>
<?php if ($totalRec > 0): ?><div class="sub"><?= $this->te('≈ {betrag} / Monat', ['betrag' => $money($totalRec / 12)]) ?></div><?php endif; ?>
</td>
</tr>
</table>
<?php if (empty($rows)): ?>
<div class="empty"><?= $this->te('Keine Positionen in der Kalkulation.') ?></div>
<?php else: ?>
<table class="main">
<thead>
<tr>
<th scope="col" style="width:8%"><?= $this->te('Menge') ?></th>
<th scope="col" style="width:50%"><?= $this->te('Artikel') ?></th>
<th scope="col" style="width:16%"><?= $this->te('Abrechnung') ?></th>
<th scope="col" class="num" style="width:13%"><?= $this->t('Einzel') ?> (<?= $priceLabel ?>)</th>
<th scope="col" class="num" style="width:13%"><?= $this->t('Summe') ?> (<?= $priceLabel ?>)</th>
</tr>
</thead>
<tbody>
<?php foreach ($rows as $r): $pv = $r['price_' . $disp]; $q = (int)$r['quantity']; ?>
<tr>
<td><?= $q ?>×</td>
<td><?= $this->e($r['name']) ?><?php if (!empty($r['required'])): ?><span class="pill-req"><?= $this->te('Pflicht') ?></span><?php endif; ?></td>
<td><?= $this->e($billingLabels[$r['billing_cycle'] ?? 'once'] ?? 'einmalig') ?></td>
<td class="num"><?= $pv === null ? 'kein Preis' : $money($pv) ?></td>
<td class="num"><?= $pv === null ? '–' : $money((float)$pv * $q) ?></td>
</tr>
<?php endforeach; ?>
<tr class="subtotal">
<td colspan="4"><strong><?= $this->te('Einmalige Kosten') ?></strong></td>
<td class="num"><strong><?= $money($totalOnce) ?></strong></td>
</tr>
<tr class="total-row">
<td colspan="4"><?= $this->t('Laufende Kosten pro Jahr') ?><?= $totalRec > 0 ? ' (≈ ' . $money($totalRec / 12) . '/Monat)' : '' ?></td>
<td class="num"><?= $money($totalRec) ?></td>
</tr>
</tbody>
</table>
<?php endif; ?>
<div class="footer"><?= $appName ?> • <?= $this->te('Unverbindliche Richtpreise, Änderungen vorbehalten') ?> • <?= $today ?></div>
</body>
</html>