Add discounts, fields, and PDF/email support to manual invoices.

This commit is contained in:
2025-12-04 15:02:19 +01:00
parent 924f8c7f87
commit e310ae4bf8
13 changed files with 812 additions and 125 deletions

View File

@@ -8,6 +8,43 @@ $net_total = $invoice->total;
$gross_total = $invoice->total_gross;
$is_credit = $net_total < 0;
// Check if any position has a discount to conditionally show the discount column
$hasDiscount = false;
foreach($invoice->positions as $p) {
if (($p->discount ?? 0) > 0) {
$hasDiscount = true;
break;
}
}
$gesamtrabatt = $invoice->gesamtrabatt ?? 0;
$subtotal = 0;
foreach($invoice->positions as $p) {
$subtotal += $p->price_total ?? 0;
}
// Group positions by position_group
$groupedPositions = [];
$hasGroups = false;
foreach($invoice->positions as $p) {
if (!empty($p->position_group)) {
$hasGroups = true;
}
}
// If no positions have groups, put all in default (no group header will be shown)
if (!$hasGroups) {
$groupedPositions['_default'] = $invoice->positions;
} else {
foreach($invoice->positions as $p) {
$group = $p->position_group ?? 'Sonstige';
if (!isset($groupedPositions[$group])) {
$groupedPositions[$group] = [];
}
$groupedPositions[$group][] = $p;
}
}
$this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
?>
@@ -48,12 +85,18 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
height: 28px;
}
#invoiceTable tr *:nth-child(5),
#invoiceTable tr *:nth-child(2),
#invoiceTable tr *:nth-child(4),
#invoiceTable tr *:nth-child(3) {
#invoiceTable tr *:nth-child(5),
#invoiceTable tr *:nth-child(6),
#invoiceTable tr *:nth-child(7) {
text-align: right;
}
#invoiceTable tr *:nth-child(3) {
text-align: center;
}
#invoiceTable tr *:not(:first-child) {
padding: 4px 0;
}
@@ -72,7 +115,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
}
#invoiceTable tr td:first-child {
max-width: 200pt;
max-width: 280pt;
}
</style>
@@ -84,88 +127,77 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<h2 style="text-align: center;color: #005384">Ihre Xinon <?=($is_credit) ? "Gutschrift" : "Rechnung"?> vom <?=date("d.m.Y",$invoice->invoice_date)?></h2>
<?php if($invoice->einleitender_text ?? ''): ?>
<p style="margin-top: 10pt; margin-bottom: 20pt; text-align: center; font-weight: bold;"><?=nl2br(htmlspecialchars($invoice->einleitender_text))?></p>
<?php endif; ?>
<table style="border-collapse: collapse; width: 100%;" id="invoiceTable">
<tr style="font-weight: bold; border-bottom: 1px solid black;" class="uneven">
<th style="text-align: center">Leistung / Produkt</th>
<th style="text-align: center">Zeitraum</th>
<th style="text-align: right">Preis</th>
<th style="text-align: center">Menge</th>
<?php if($hasDiscount): ?><th style="text-align: right">Rabatt %</th><?php endif; ?>
<th style="text-align: right">Netto €</th>
<th style="text-align: right">Ust. %</th>
<th style="text-align: right; padding-right: 4pt">Brutto €</th>
</tr>
<?php
$i = 0;
foreach($invoice->positions as $p):
$timerange_month_only = method_exists($p, 'getOption') ? $p->getOption('timerange_month_only') : (isset($p->options) ? (json_decode($p->options, true)['timerange_month_only'] ?? false) : false);
// Handle dates safely
$start_date = null;
$end_date = null;
if (!empty($p->start_date)) {
try {
$start_date = new DateTime($p->start_date);
} catch (Exception $e) {
$start_date = null;
}
}
if (!empty($p->end_date)) {
try {
$end_date = new DateTime($p->end_date);
} catch (Exception $e) {
$end_date = $start_date;
}
} else {
$end_date = $start_date;
}
foreach($groupedPositions as $groupName => $positions):
?>
<!-- Group Header (only show if not default) -->
<?php if ($groupName !== '_default'): ?>
<tr style="background-color: #d9d9d9; font-weight: bold;">
<td colspan="<?=$hasDiscount ? '7' : '6'?>" style="padding: 6px 4pt; border-top: 1px solid black; text-align: left;">
<?=htmlspecialchars($groupName)?>
</td>
</tr>
<?php endif; ?>
<?php
foreach($positions as $p):
$amount = (float) number_format($p->amount ?? 0, 3, ",", ".");
$unit = htmlspecialchars($p->unit ?? 'Stk.');
$price = number_format($p->price ?? 0, 2, ",",".");
$discount = $p->discount ?? 0;
$price_total = number_format($p->price_total ?? 0, 2, ",",".");
$price_gross = number_format($p->price_gross ?? 0, 2, ",",".");
$vatrate = number_format($p->vatrate ?? 0, 0, ",",".");
?>
<tr class="position <?=($i%2 == 0) ? "even" : "uneven" ?>">
<td>
<td style="padding-left: 4pt; vertical-align: top;">
<?=htmlspecialchars($p->product_name ?? '')?>
<?php if(isset($p->product_info) && $p->product_info): ?>
<div style="padding-left: 12pt; font-size: 10px; color: #666;"><?=htmlspecialchars($p->product_info)?></div>
<?php endif; ?>
<?php if(isset($p->matchcode) && $p->matchcode): ?>
<div style="padding-left: 12pt"><?=htmlspecialchars($p->matchcode)?></div>
<div style="padding-left: 12pt; font-size: 10px; color: #666;"><?=htmlspecialchars($p->matchcode)?></div>
<?php endif; ?>
</td>
<td style="text-align: center;">
<?php if($start_date && $end_date): ?>
<?php if($timerange_month_only): ?>
<?=$start_date->format("m.Y")?>
<?php elseif(isset($p->billing_period) && $p->billing_period > 1): ?>
<?=$start_date->format("m.Y")?> - <?=$end_date->format("m.Y") ?>
<?php else: ?>
<?php if($start_date->format("d.m.Y") == $end_date->format("d.m.Y")): ?>
<?=$start_date->format("d.m.Y")?>
<?php else: ?>
<?=$start_date->format("d.m.Y")?> - <?=$end_date->format("d.m.Y") ?>
<?php endif; ?>
<?php endif; ?>
<?php elseif($start_date): ?>
<?=$start_date->format("d.m.Y")?>
<?php else: ?>
-
<?php endif; ?>
</td>
<td><?=$price?> €</td>
<td style="text-align: center"><?=$amount?></td>
<td><?=$price_total?> €</td>
<td style="text-align: right;"><?=$vatrate?>%</td>
<td style="padding-right: 4pt;"><?=$price_gross?> €</td>
<td style="text-align: right; padding: 4px 0;"><?=$price?> €</td>
<td style="text-align: center; padding: 4px 0;"><?=$amount?> <?=$unit?></td>
<?php if($hasDiscount): ?><td style="text-align: right; padding: 4px 0;"><?=number_format($discount, 2, ",", ".")?>%</td><?php endif; ?>
<td style="text-align: right; padding: 4px 0;"><?=$price_total?> €</td>
<td style="text-align: right; padding: 4px 0;"><?=$vatrate?>%</td>
<td style="text-align: right; padding: 4px 0; padding-right: 4pt;"><?=$price_gross?> €</td>
</tr>
<?php
$i++;
endforeach;
endforeach;
?>
<tr style="font-weight: bold; background-color: #ebebeb; border-bottom: 1px solid black;border-top: 1px solid black">
<td colspan="5">Gesamt Netto:</td>
<?php if($gesamtrabatt > 0): ?>
<tr style="background-color: #ebebeb; border-top: 2px solid black;">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Zwischensumme:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($subtotal, 2, ",","."). " €"?></td>
</tr>
<tr style="background-color: #ebebeb; border-bottom: 1px solid #ccc;">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamtrabatt <?=number_format($gesamtrabatt, 2, ",", ".")?>%:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt; color: #d32f2f;">-<?=number_format($subtotal * ($gesamtrabatt / 100), 2, ",","."). " €"?></td>
</tr>
<?php endif; ?>
<tr style="font-weight: bold; background-color: #ebebeb; border-bottom: 1px solid black;<?=($gesamtrabatt > 0) ? '' : 'border-top: 2px solid black;'?>">
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamt Netto:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($net_total, 2, ",","."). " €"?></td>
</tr>
@@ -173,7 +205,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<?php if($rate > 0): ?>
<tr style="font-size: 11px;border-bottom: 1px solid black;">
<td colspan="5">USt. <?=number_format($rate, 0, ",", ".")?>%:</td>
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">USt. <?=number_format($rate, 0, ",", ".")?>%:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($vat_total, 2, ",","."). " €"?></td>
</tr>
<?php endif; ?>
@@ -182,7 +214,7 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<!-- double underline border on bottom -->
<tr style="font-weight: bold; border-bottom: 3px double black; background-color: #ebebeb;">
<td colspan="5">Gesamt Brutto:</td>
<td colspan="<?=$hasDiscount ? '5' : '4'?>" style="padding: 4px 0;">Gesamt Brutto:</td>
<td colspan="2" style="text-align: right; padding-right: 4pt;"><?=number_format($gross_total, 2, ",","."). " €"?></td>
</tr>
</table>
@@ -193,14 +225,31 @@ $this->setReturnValue(['filename' => $invoice->invoice_number . ".pdf"]);
<p style="font-weight: bold;"><?=$invoice->tax_text?></p>
<?php endif; ?>
<?php if($is_credit): ?>
<p style="color: #FF0000; font-weight: bold">Gutschrift! Bitte nicht überweisen.</p>
<p style="color: #FF0000; font-weight: bold; text-align: center;">Gutschrift! Bitte nicht überweisen.</p>
<?php elseif($invoice->billing_type == "sepa"): ?>
<p style="color: #FF0000; font-weight: bold">BITTE NICHT EINZAHLEN, DER BETRAG WIRD AUTOMATISCH VON IHREM KONTO ABGEBUCHT !</p>
<p style="color: #FF0000; font-weight: bold; text-align: center;">BITTE NICHT EINZAHLEN DER BETRAG WIRD AUTOMATISCH VON IHREM KONTO ABGEBUCHT!</p>
<?php else: ?>
Bitte <b>überweisen</b> Sie den Rechnungsbetrag bis zum &nbsp;<b><?=(new DateTime("@".$invoice->invoice_date))->modify("+14 days")->format("d.m.Y")?></b> auf folgendes Konto:<br />
<b style="padding-left: 4pt;">IBAN: <?=$bank_iban?></b><br />
<b style="padding-left: 4pt;">BIC: <?=$bank_bic?></b><br /><br />
Bitte geben Sie als Verwendungszweck unbedingt die Rechnungsnummer an, nur so können wir Ihre Zahlung eindeutig zuordnen
<div style="border-top: 1px solid #ccc; padding-top: 10pt; margin-top: 10pt;">
<p style="margin-bottom: 8pt;">
<strong>Zahlungsinformationen:</strong>
</p>
<p style="margin-bottom: 4pt;">
Bitte überweisen Sie den Rechnungsbetrag bis zum <strong><?=(new DateTime("@".$invoice->invoice_date))->modify("+14 days")->format("d.m.Y")?></strong> auf folgendes Konto:
</p>
<table style="margin-left: 20pt; margin-bottom: 12pt;">
<tr><td style="width: 100pt;"><strong>IBAN:</strong></td><td><?=$bank_iban?></td></tr>
<tr><td><strong>BIC:</strong></td><td><?=$bank_bic?></td></tr>
<tr><td><strong>Bank:</strong></td><td><?=$bank_bank?></td></tr>
</table>
<div style="background-color: #f5f5f5; padding: 10pt; border-left: 3px solid #005384; margin-top: 12pt;">
<p style="margin: 0; margin-bottom: 4pt; font-size: 14px;">
<strong>Verwendungszweck: <?=$invoice->invoice_number ?? "VORSCHAU"?></strong>
</p>
<p style="margin: 0; font-size: 10px; color: #666;">
Wichtig: Bitte geben Sie den oben angeführten Verwendungszweck bei der Überweisung an, damit wir Ihre Zahlung eindeutig zuordnen können.
</p>
</div>
</div>
<?php endif; ?>
</div>