OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

pricelist.php

7 KB
<?php
/**
 * Preisliste als PDF (Dompdf, Landscape A4). Optik 1:1 wie der Vertrags-Export
 * (/contracts/export/pdf): dunkelblaue Kopfzeile, Summary-Boxen, Kategorie-
 * Kopfzeilen mit Farbpunkt und Anzahl, Fußzeile. Druck-/PDF-Kontext →
 * Hex-Farben laut STYLEGUIDE.md zulässig.
 *
 * Erwartete Variablen: $grouped, $display, $billing, $app_name,
 * $category_name.
 */
$money = fn($v) => $v === null ? '–' : $this->moneyHtml($v);
$billingLabels = $billing ?? ['once' => 'einmalig', 'monthly' => 'pro Monat', 'yearly' => 'pro Jahr'];
$grouped = $grouped ?? [];
$today   = date('d.m.Y H:i');
$appName = $this->e($app_name ?? 'OpenNIT');
$itemCount = array_sum(array_map('count', $grouped));
$catCount  = count($grouped);
// Deterministische Kategoriefarbe (Punkt vor der Kategorie-Kopfzeile) – wie im
// Vertrags-Export.
$palette = ['#4f46e5', '#059669', '#d97706', '#dc2626', '#2563eb', '#7c3aed', '#0891b2', '#db2777'];
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title><?= $this->te('Preisliste') ?><?= !empty($category_name) ? ' – ' . $this->e($category_name) : '' ?></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: 4px; }
.disclaimer { font-size: 8pt; color: #6b7280; margin: 0 0 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: 33%; text-align: center; }
.summary-cell .label { font-size: 7.5pt; color: #6b7280; text-transform: uppercase; letter-spacing: 0.04em; }
.summary-cell .value { font-size: 14pt; font-weight: bold; color: #1a4a7a; }
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.cat-header td { background: #f3f4f6; font-weight: bold; font-size: 8.5pt; padding: 6px 6px; border-top: 2px solid #d1d5db; }
.cat-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.cat-count { font-size: 7.5pt; color: #6b7280; font-weight: normal; margin-left: 6px; }
.art-name { font-weight: bold; }
.art-sub { color: #6b7280; font-size: 7.5pt; }
.req { color: #4b5563; font-size: 7.5pt; margin-top: 1px; }
.req b { color: #1a4a7a; }
.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->t('Preisliste') ?><?= !empty($category_name) ? ' – ' . $this->e($category_name) : '' ?> <span class="price-mode"><?= $this->te('Richtpreise') ?></span></h1>
<div class="meta"><?= $this->t('Exportiert am') ?> <?= $today ?> &bull; <?= $appName ?></div>
<div class="disclaimer"><?= $this->te('Tagesaktuelle, unverbindliche Richtpreise &ndash; können sich täglich ändern. Die Spalte „Stand" nennt das Gültig-ab-Datum. Als „Pflicht" gekennzeichnete Positionen sind für den Betrieb zwingend erforderlich.') ?></div>

<table class="summary-box">
  <tr>
    <td class="summary-cell">
      <div class="label"><?= $this->te('Artikel') ?></div>
      <div class="value"><?= (int)$itemCount ?></div>
    </td>
    <td class="summary-cell" style="border-left:none">
      <div class="label"><?= $this->te('Kategorien') ?></div>
      <div class="value"><?= (int)$catCount ?></div>
    </td>
    <td class="summary-cell" style="border-left:none">
      <div class="label"><?= $this->te('Stand') ?></div>
      <div class="value"><?= date('d.m.Y') ?></div>
    </td>
  </tr>
</table>

<?php if (empty($grouped)): ?>
<div class="empty"><?= $this->te('Keine Artikel vorhanden.') ?></div>
<?php else: ?>
<table class="main">
  <thead>
    <tr>
      <th scope="col" style="width:42%"><?= $this->te('Artikel') ?></th>
      <th scope="col" style="width:8%"><?= $this->te('Einheit') ?></th>
      <th scope="col" style="width:12%"><?= $this->te('Abrechnung') ?></th>
      <th scope="col" class="num" style="width:12%"><?= $this->te('Netto') ?></th>
      <th scope="col" class="num" style="width:12%"><?= $this->te('Brutto') ?></th>
      <th scope="col" class="num" style="width:14%"><?= $this->te('Stand') ?></th>
    </tr>
  </thead>
  <tbody>
  <?php $ci = 0; foreach ($grouped as $catName => $rows): $dot = $palette[$ci % count($palette)]; $ci++; ?>
    <tr class="cat-header">
      <td colspan="6">
        <span class="cat-dot" style="background:<?= $dot ?>"></span><?= $this->e($catName) ?>
        <span class="cat-count"><?= $this->te('{anzahl} Artikel', ['anzahl' => count($rows)]) ?></span>
      </td>
    </tr>
    <?php foreach ($rows as $it): ?>
    <tr>
      <td>
        <div class="art-name"><?= $this->e($it['name']) ?></div>
        <?php $hm = trim((string)($it['manufacturer'] ?? '') . ' ' . (string)($it['model'] ?? '')); ?>
        <?php if ($hm !== '' || !empty($it['sku'])): ?>
        <div class="art-sub"><?= $this->e($hm) ?><?php if (!empty($it['sku'])): ?><?= $hm !== '' ? ' &middot; ' : '' ?><?= $this->te('Art.-Nr. {nummer}', ['nummer' => $it['sku']]) ?><?php endif; ?></div>
        <?php endif; ?>
        <?php foreach (($it['requirements'] ?? []) as $rq): $rp = $rq['price_net']; ?>
        <div class="req"><b><?= $this->te('Pflicht:') ?></b> <?= (int)$rq['quantity'] ?>&times; <?= $this->e($rq['name']) ?><?php if ($rp !== null): ?> (<?= $money($rp) ?><?php echo ($rq['billing_cycle'] ?? 'once') !== 'once' ? ' ' . $this->e($billingLabels[$rq['billing_cycle']] ?? '') : ''; ?>)<?php endif; ?></div>
        <?php endforeach; ?>
      </td>
      <td><?= $this->e($it['unit'] ?? 'Stück') ?></td>
      <td><?= $this->e($billingLabels[$it['billing_cycle'] ?? 'once'] ?? 'einmalig') ?></td>
      <td class="num"><?= $it['price_net'] === null ? '–' : $money($it['price_net']) ?></td>
      <td class="num"><?= $it['price_gross'] === null ? '–' : $money($it['price_gross']) ?></td>
      <td class="num"><?= $it['valid_from'] === null ? '–' : date('d.m.Y', strtotime((string)$it['valid_from'])) ?></td>
    </tr>
    <?php endforeach; ?>
  <?php endforeach; ?>
  </tbody>
</table>
<?php endif; ?>

<div class="footer"><?= $appName ?> &bull; <?= $this->te('Unverbindliche Richtpreise, Änderungen vorbehalten') ?> &bull; <?= $today ?></div>
</body>
</html>