243 lines
11 KiB
PHP
243 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* Template for Warehouse Offer PDF Main Content
|
|
* All variables are passed from WarehouseOfferController->createPDFAction
|
|
*/
|
|
|
|
// --- Helper Functions ---
|
|
function formatPrice($price, $currency = '€') {
|
|
return number_format($price, 2, ',', '.') . ' ' . $currency;
|
|
}
|
|
|
|
// --- Text Elements ---
|
|
$texts = [
|
|
'DE' => [
|
|
'title' => 'Angebot',
|
|
'offerNumberLabel' => 'Angebotsnr.:',
|
|
'offerDateLabel' => 'Datum:',
|
|
'editorLabel' => 'Sachbearbeiter:',
|
|
'validUntilLabel' => 'Gültig bis:',
|
|
'table' => [
|
|
'pos' => 'Pos',
|
|
'article' => 'Artikel / Beschreibung',
|
|
'amount' => 'Menge',
|
|
'unit' => 'Einheit',
|
|
'unitPrice' => 'Einzelpreis',
|
|
'discount' => 'Rabatt',
|
|
'totalPrice' => 'Gesamtpreis'
|
|
],
|
|
'summary' => [
|
|
'subTotal' => 'Nettobetrag',
|
|
'discount' => 'Rabatt',
|
|
'vatFormatted' => 'zzgl. {VAT_RATE}% MwSt.',
|
|
'total' => 'Gesamtbetrag',
|
|
'alternativeTotal' => 'Summe Alternativpositionen'
|
|
],
|
|
'purpose' => 'Zweck / Projekt',
|
|
'alternativeHeader' => 'Alternativpositionen',
|
|
'notes' => 'Anmerkungen & Konditionen',
|
|
'defaultOfferText' => 'Vielen Dank für Ihre Anfrage. Es gelten unsere Allgemeinen Geschäftsbedingungen.',
|
|
]
|
|
];
|
|
$lang = 'DE';
|
|
$text = $texts[$lang];
|
|
|
|
// --- Calculations ---
|
|
$discountAmount = 0;
|
|
$subTotalAfterDiscount = $subTotal;
|
|
$vatAmount = 0;
|
|
|
|
if (isset($offer->totalDiscount) && $offer->totalDiscount > 0) {
|
|
$discountPercentage = $offer->totalDiscount;
|
|
$discountAmount = round(($subTotal * $discountPercentage) / 100, 2, PHP_ROUND_HALF_UP);
|
|
$subTotalAfterDiscount = round($subTotal - $discountAmount, 2, PHP_ROUND_HALF_UP);
|
|
}
|
|
|
|
$grandTotal = $subTotalAfterDiscount;
|
|
|
|
if ($includeTax) {
|
|
$vatAmount = round($subTotalAfterDiscount * $vatRate, 2, PHP_ROUND_HALF_UP);
|
|
$grandTotal = round($subTotalAfterDiscount + $vatAmount, 2, PHP_ROUND_HALF_UP);
|
|
}
|
|
|
|
$formattedOfferDate = date("d.m.Y", $offerDate);
|
|
$validityDays = isset($validity) ? (int)$validity : 14;
|
|
$formattedValidUntil = date("d.m.Y", strtotime("+$validityDays days", $offerDate));
|
|
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title><?= $text['title'] ?> <?= $offerNumber ?></title>
|
|
<meta charset="utf-8"/>
|
|
<style>
|
|
body { font-family: "Open Sans", sans-serif, Verdana; font-size: 10px; color: #333; }
|
|
h1 { text-align: center; color: #005384; font-size: 18px; margin-bottom: 20px; }
|
|
.header-info table { width: 100%; border-collapse: collapse; font-size: 11px; margin-bottom: 12px; }
|
|
.header-info td { padding: 2px 5px; }
|
|
.header-info .label { font-weight: bold; text-align: right; padding-right: 10px; width: 120px; }
|
|
|
|
#positionsTable { width: 100%; border-collapse: collapse; margin-top: 8px; margin-bottom: 15px; }
|
|
#positionsTable th { border-bottom: 2px solid #005384; padding: 8px 4px; text-align: left; background-color: #f2f2f2; font-size: 10px; }
|
|
#positionsTable td { border-bottom: 1px solid #e1e1e1; padding: 6px 4px; vertical-align: top; }
|
|
|
|
#positionsTable th.pos, #positionsTable td.pos { text-align: center; width: 30px; }
|
|
#positionsTable th.amount, #positionsTable td.amount { text-align: right; width: 50px;}
|
|
#positionsTable th.unit, #positionsTable td.unit { text-align: center; width: 40px;}
|
|
#positionsTable th.price, #positionsTable td.price { text-align: right; width: 80px;}
|
|
#positionsTable th.discount, #positionsTable td.discount { text-align: right; width: 50px;}
|
|
#positionsTable th.total, #positionsTable td.total { text-align: right; width: 90px;}
|
|
|
|
.position-group-header td { background-color: #e8f0f8; font-weight: bold; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; padding: 4px; }
|
|
.alternative-group-header td { background-color: #fff8e1; font-weight: bold; font-style: italic; }
|
|
|
|
.article-title { font-weight: bold; }
|
|
.article-description { font-size: 9px; color: #555; padding-left: 10px; margin-top: 2px; }
|
|
.position-comment { font-size: 9px; color: #444; padding-left: 10px; margin-top: 4px; font-style: italic; }
|
|
.alternative-position { font-style: italic; color: #555; }
|
|
|
|
/* The summary table no longer needs to float */
|
|
#summaryTable { width: 100%; border-collapse: collapse; margin-top: 10px; font-size: 11px; }
|
|
#summaryTable td { padding: 5px 8px; }
|
|
#summaryTable td.label { text-align: right; font-weight: bold; }
|
|
#summaryTable td.value { text-align: right; }
|
|
#summaryTable tr.grand-total td { border-top: 1px solid #333; border-bottom: 3px double #333; font-weight: bold; font-size: 12px; }
|
|
#summaryTable tr.subtotal td { border-top: 1px solid #ccc; }
|
|
|
|
.footer-container { page-break-inside: avoid; }
|
|
.offer-text { padding-top: 20px; border-top: 1px solid #eee; margin-top: 20px; font-size: 10px; }
|
|
.alternative-summary { font-size: 10px; padding: 5px; border: 1px dashed #ccc; background-color: #fafafa; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<h1><?= $text['title'] ?></h1>
|
|
|
|
<div class="header-info">
|
|
<table>
|
|
<tr>
|
|
<td class="label" style="text-align: left"><?= $text['offerNumberLabel'] ?></td>
|
|
<td><?= htmlspecialchars($offerNumber) ?> - v<?= isset($offer->version) ? $offer->version : 1 ?></td>
|
|
<td class="label"><?= $text['offerDateLabel'] ?></td>
|
|
<td><?= $formattedOfferDate ?></td>
|
|
</tr>
|
|
<tr>
|
|
<td class="label" style="text-align: left"><?= $text['editorLabel'] ?></td>
|
|
<td><?= htmlspecialchars($offerEditorName) ?></td>
|
|
<td class="label"><?= $text['validUntilLabel'] ?></td>
|
|
<td><?= $formattedValidUntil ?></td>
|
|
</tr>
|
|
<?php if (!empty($offer->purpose)): ?>
|
|
<tr>
|
|
<td class="label" style="text-align: left; vertical-align: top; padding-top: 12px;"><?= $text['purpose'] ?></td>
|
|
<td colspan="3" style="padding-top: 12px;"><?= nl2br(htmlspecialchars($offer->purpose)) ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
</table>
|
|
</div>
|
|
|
|
<table id="positionsTable">
|
|
<thead>
|
|
<tr>
|
|
<th class="pos"><?= $text['table']['pos'] ?></th>
|
|
<th class="article"><?= $text['table']['article'] ?></th>
|
|
<th class="amount"><?= $text['table']['amount'] ?></th>
|
|
<th class="unit"><?= $text['table']['unit'] ?></th>
|
|
<th class="price"><?= $text['table']['unitPrice'] ?></th>
|
|
<th class="discount"><?= $text['table']['discount'] ?></th>
|
|
<th class="total"><?= $text['table']['totalPrice'] ?></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php
|
|
$posCounter = 0;
|
|
foreach ($entries as $groupName => $positions):
|
|
$isAlternativeGroup = ($groupName === 'Alternativpositionen');
|
|
if (!empty($groupName)): ?>
|
|
<tr class="position-group-header <?= $isAlternativeGroup ? 'alternative-group-header' : '' ?>">
|
|
<td colspan="7"><?= htmlspecialchars($isAlternativeGroup ? $text['alternativeHeader'] : $groupName) ?></td>
|
|
</tr>
|
|
<?php endif;
|
|
|
|
foreach ($positions as $p):
|
|
if ($p['amount'] == 0) continue;
|
|
$posCounter++;
|
|
$rowClass = $isAlternativeGroup ? 'alternative-position' : '';
|
|
?>
|
|
<tr class="<?= $rowClass ?>">
|
|
<td class="pos"><?= $posCounter ?></td>
|
|
<td class="article">
|
|
<div class="article-title"><?= htmlspecialchars($p['articleText']) ?></div>
|
|
<?php if (!empty($p['articleDescription'])): ?><div class="article-description"><?= nl2br(htmlspecialchars($p['articleDescription'])) ?></div><?php endif; ?>
|
|
<?php if (!empty($p['comment'])): ?><div class="position-comment"> <?= nl2br(htmlspecialchars($p['comment'])) ?></div><?php endif; ?>
|
|
</td>
|
|
<td class="amount"><?= number_format($p['amount'], 2, ',', '.') ?></td>
|
|
<td class="unit"><?= htmlspecialchars($p['articleUnit']) ?></td>
|
|
<td class="price"><?= formatPrice($p['price'], '€') ?></td>
|
|
<td class="discount"><?= htmlspecialchars($p['discount'] . '%') ?></td>
|
|
<td class="total"><?= formatPrice($p['totalPrice'], '€') ?></td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="footer-container">
|
|
<table style="width: 100%;">
|
|
<tbody>
|
|
<tr>
|
|
<td style="width: 55%; vertical-align: top; padding-right: 20px;">
|
|
<?php if ($alternativeTotal > 0): ?>
|
|
<div class="alternative-summary">
|
|
<strong><?= $text['summary']['alternativeTotal'] ?>:</strong>
|
|
<span style="float: right;"><?= formatPrice($alternativeTotal, '€') ?></span>
|
|
<br>
|
|
<small>(nicht im Gesamtbetrag enthalten)</small>
|
|
</div>
|
|
<?php endif; ?>
|
|
</td>
|
|
<td style="width: 45%; vertical-align: top;">
|
|
<table id="summaryTable">
|
|
<tbody>
|
|
<tr class="subtotal">
|
|
<td class="label">Zwischensumme:</td>
|
|
<td class="value"><?= formatPrice($subTotal, '€') ?></td>
|
|
</tr>
|
|
<?php if ($discountAmount > 0): ?>
|
|
<tr>
|
|
<td class="label"><?= $text['summary']['discount'] ?> (<?= rtrim(rtrim(number_format($discountPercentage, 3), '0'), '.') ?>%):</td>
|
|
<td class="value">-<?= formatPrice($discountAmount, '€') ?></td>
|
|
</tr>
|
|
<tr class="subtotal">
|
|
<td class="label">Zwischensumme (nach Rabatt):</td>
|
|
<td class="value"><?= formatPrice($subTotal - $discountAmount, '€') ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
<?php if ($includeTax):
|
|
$vatLabel = str_replace('{VAT_RATE}', number_format($vatRate * 100, 0), $text['summary']['vatFormatted']);
|
|
?>
|
|
<tr>
|
|
<td class="label"><?= $vatLabel ?>:</td>
|
|
<td class="value"><?= formatPrice($vatAmount, '€') ?></td>
|
|
</tr>
|
|
<?php endif; ?>
|
|
<tr class="grand-total">
|
|
<td class="label"><?= $text['summary']['total'] ?>:</td>
|
|
<td class="value"><?= formatPrice($grandTotal, '€') ?></td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<div class="offer-text">
|
|
<strong><?= $text['notes'] ?></strong>
|
|
<div><?= nl2br(htmlspecialchars($closingText)) ?></div>
|
|
</div>
|
|
</div>
|
|
|
|
</body>
|
|
</html>
|