diff --git a/Abrechnung/module.php b/Abrechnung/module.php
index 65ed869..cc9f37c 100644
--- a/Abrechnung/module.php
+++ b/Abrechnung/module.php
@@ -63,7 +63,7 @@ class Abrechnung extends IPSModule
$this->RegisterVariableInteger('FromDate', 'Startdatum', '~UnixTimestamp', 1);
$this->RegisterVariableInteger('ToDate', 'Enddatum', '~UnixTimestamp', 2);
$this->RegisterVariableString('LastResult', 'Letzte Abrechnung', '', 3);
- $this->RegisterVariableString('VisuTariffOverview', 'Tarifübersicht', '~HTMLBox', 10);
+ $this->RegisterVariableString('VisuTariffOverview', 'Tarife bearbeiten', '~HTMLBox', 10);
$this->RegisterVariableInteger('VisuTariffType', 'Tarifart', 'BELEVO.TariffType', 11);
$this->RegisterVariableInteger('VisuTariffStart', 'Tarif Startdatum', '~UnixTimestamp', 12);
$this->RegisterVariableInteger('VisuTariffEnd', 'Tarif Enddatum', '~UnixTimestamp', 13);
@@ -81,16 +81,7 @@ class Abrechnung extends IPSModule
'Abrechnung starten',
"InstanceID . ", 'StartBilling', ''); ?>"
);
- $this->RegisterScript(
- 'SaveVisuTariff',
- 'Tarif speichern',
- "InstanceID . ", 'SaveVisuTariff', ''); ?>"
- );
- $this->RegisterScript(
- 'DeleteVisuTariff',
- 'Tarif löschen',
- "InstanceID . ", 'DeleteVisuTariff', ''); ?>"
- );
+ $this->EnsureVisuTariffObjects();
$this->RegisterMediaDocument('InvoicePDF', 'Letzte Rechnung', 'pdf');
}
@@ -105,7 +96,7 @@ class Abrechnung extends IPSModule
private function EnsureVisuTariffObjects()
{
- $this->RegisterVariableString('VisuTariffOverview', 'Tarifübersicht', '~HTMLBox', 10);
+ $this->RegisterVariableString('VisuTariffOverview', 'Tarife bearbeiten', '~HTMLBox', 10);
$this->RegisterVariableInteger('VisuTariffType', 'Tarifart', 'BELEVO.TariffType', 11);
$this->RegisterVariableInteger('VisuTariffStart', 'Tarif Startdatum', '~UnixTimestamp', 12);
$this->RegisterVariableInteger('VisuTariffEnd', 'Tarif Enddatum', '~UnixTimestamp', 13);
@@ -117,16 +108,17 @@ class Abrechnung extends IPSModule
$this->EnableAction('VisuTariffEnd');
$this->EnableAction('VisuTariffPrice');
- $this->RegisterScript(
- 'SaveVisuTariff',
- 'Tarif speichern',
- "InstanceID . ", 'SaveVisuTariff', ''); ?>"
- );
- $this->RegisterScript(
- 'DeleteVisuTariff',
- 'Tarif löschen',
- "InstanceID . ", 'DeleteVisuTariff', ''); ?>"
- );
+ foreach (['VisuTariffType', 'VisuTariffStart', 'VisuTariffEnd', 'VisuTariffPrice', 'VisuTariffStatus', 'SaveVisuTariff', 'DeleteVisuTariff'] as $ident) {
+ $this->HideObjectByIdent($ident);
+ }
+ }
+
+ private function HideObjectByIdent($Ident)
+ {
+ $id = @IPS_GetObjectIDByIdent($Ident, $this->InstanceID);
+ if ($id !== false) {
+ IPS_SetHidden($id, true);
+ }
}
private function RegisterTariffVariableProfiles()
@@ -329,34 +321,183 @@ class Abrechnung extends IPSModule
return;
}
+ $types = $this->GetTariffTypes();
$tariffs = $this->ReadTariffs();
- $html = "
-
- | Tarifart |
- Gültig von |
- Gültig bis |
- Preis |
-
";
+ $rows = '';
- if (empty($tariffs)) {
- $html .= "| Keine Tarife erfasst. |
";
- } else {
- foreach ($tariffs as $tariff) {
- $start = $this->toUnixTs($tariff['start'] ?? 0, false);
- $end = $this->toUnixTs($tariff['end'] ?? 0, false);
- $html .= "
- | " . $this->h($tariff['unit_type'] ?? '') . " |
- " . ($start ? date('d.m.Y', $start) : '-') . " |
- " . ($end ? date('d.m.Y', $end) : '-') . " |
- " . number_format((float)($tariff['price'] ?? 0), 3, '.', "'") . " Rp/Einheit |
-
";
- }
+ foreach ($tariffs as $tariff) {
+ $rows .= $this->BuildTariffEditorRow(
+ (string)($tariff['unit_type'] ?? 'Netztarif'),
+ $this->FormatDateInput($this->toUnixTs($tariff['start'] ?? 0, false)),
+ $this->FormatDateInput($this->toUnixTs($tariff['end'] ?? 0, false)),
+ number_format((float)($tariff['price'] ?? 0), 3, '.', '')
+ );
}
- $html .= '
';
+ if ($rows === '') {
+ $rows = $this->BuildTariffEditorRow('Netztarif', date('Y-01-01'), date('Y-12-31'), '0.000');
+ }
+
+ $typeOptions = '';
+ foreach ($types as $type) {
+ $typeOptions .= '';
+ }
+
+ $instanceID = (int)$this->InstanceID;
+ $status = $this->h(@GetValue($this->GetIDForIdent('VisuTariffStatus')));
+
+ $html = "
+
+
+
+
+
+| Tarifart |
+Startdatum |
+Enddatum |
+Rp/Einheit |
+Aktion |
+
+
+" . $rows . "
+
+
+
+
+" . $status . "
+
+
" . $this->BuildTariffEditorRow('Netztarif', date('Y-01-01'), date('Y-12-31'), '0.000') . "
+
+
";
+
SetValue($overviewID, $html);
}
+ private function BuildTariffEditorRow($type, $start, $end, $price)
+ {
+ $options = '';
+ foreach ($this->GetTariffTypes() as $tariffType) {
+ $selected = strtolower($tariffType) === strtolower((string)$type) ? ' selected' : '';
+ $options .= '';
+ }
+
+ return ''
+ . ' | '
+ . ' | '
+ . ' | '
+ . ' | '
+ . ' | '
+ . '
';
+ }
+
+ private function SaveTariffTableFromPayload($payload)
+ {
+ $rows = json_decode((string)$payload, true);
+ if (!is_array($rows)) {
+ throw new Exception('Ungültige Tarifdaten.');
+ }
+
+ $allowedTypes = $this->GetTariffTypes();
+ $tariffs = [];
+
+ foreach ($rows as $row) {
+ if (!is_array($row)) {
+ continue;
+ }
+
+ $type = trim((string)($row['unit_type'] ?? ''));
+ $startText = trim((string)($row['start'] ?? ''));
+ $endText = trim((string)($row['end'] ?? ''));
+ $priceText = str_replace(',', '.', trim((string)($row['price'] ?? '')));
+
+ if ($type === '' && $startText === '' && $endText === '' && $priceText === '') {
+ continue;
+ }
+ if (!in_array($type, $allowedTypes, true)) {
+ throw new Exception('Ungültige Tarifart: ' . $type);
+ }
+ if ($startText === '' || $endText === '') {
+ throw new Exception('Start- und Enddatum müssen gesetzt sein.');
+ }
+ if (!is_numeric($priceText) || (float)$priceText < 0) {
+ throw new Exception('Tarifpreis ungültig.');
+ }
+
+ $start = strtotime($startText . ' 00:00:00');
+ $end = strtotime($endText . ' 00:00:00');
+ if ($start === false || $end === false || $end < $start) {
+ throw new Exception('Datumsbereich ungültig.');
+ }
+
+ $tariffs[] = [
+ 'start' => $start,
+ 'end' => $end,
+ 'price' => round((float)$priceText, 3),
+ 'unit_type' => $type
+ ];
+ }
+
+ $this->WriteTariffs($tariffs);
+ return count($tariffs) . ' Tarif(e) gespeichert.';
+ }
+
+ private function FormatDateInput($timestamp)
+ {
+ return $timestamp ? date('Y-m-d', (int)$timestamp) : '';
+ }
private function RegisterMediaDocument($Ident, $Name, $Extension, $Position = 0)
{
$mid = @IPS_GetObjectIDByIdent($Ident, $this->InstanceID);
@@ -406,6 +547,18 @@ class Abrechnung extends IPSModule
}
break;
+ case 'SaveTariffTable':
+ try {
+ $message = $this->SaveTariffTableFromPayload($Value);
+ SetValue($this->GetIDForIdent('VisuTariffStatus'), $message);
+ echo $message;
+ } catch (Throwable $e) {
+ $message = 'Fehler beim Speichern der Tarifliste: ' . $e->getMessage();
+ SetValue($this->GetIDForIdent('VisuTariffStatus'), $message);
+ echo $message;
+ }
+ break;
+
case 'StartBilling':
try {
$pdfContent = $this->GenerateInvoices();
@@ -479,123 +632,327 @@ public function GenerateInvoices()
private function BuildUserInvoice($pdf, $user, $power, $water, $tariffs, $from, $to)
{
+ $pdf->SetTextColor(20, 20, 20);
+ $pdf->SetDrawColor(190, 190, 190);
+ $pdf->SetLineWidth(0.15);
+ $pdf->SetFont('dejavusans', 'B', 16);
+ $pdf->SetY(max($pdf->GetY(), 38));
+ $pdf->Cell(0, 8, 'Elektro- und Nebenkostenabrechnung', 0, 1, 'L');
+
+ $pdf->SetFont('dejavusans', '', 8.5);
+ $pdf->Cell(0, 5, 'Liegenschaft: ' . $this->ReadPropertyString('PropertyText'), 0, 1, 'L');
+ $pdf->Ln(3);
+
$meterNames = [];
foreach ($power as $m) {
if (($m['user_id'] ?? null) == ($user['id'] ?? null)) {
- $meterNames[] = $this->h($m['name'] ?? '');
+ $meterNames[] = (string)($m['name'] ?? '');
}
}
foreach ($water as $m) {
if (($m['user_id'] ?? null) == ($user['id'] ?? null)) {
- $meterNames[] = $this->h($m['name'] ?? '');
+ $meterNames[] = (string)($m['name'] ?? '');
}
}
- $meterText = empty($meterNames) ? '-' : implode('
', $meterNames);
$periodText = date('d.m.Y', $from) . ' - ' . date('d.m.Y', $to);
+ $meterText = empty($meterNames) ? '-' : implode("\n", $meterNames);
+ $addressText = trim(($user['name'] ?? '') . "\n" . ($user['address'] ?? '') . "\n" . ($user['city'] ?? ''));
- $html = "
- Elektro- und Nebenkostenabrechnung
- Liegenschaft: " . $this->h($this->ReadPropertyString('PropertyText')) . "
-
-
-
- Abrechnungszeitraum " . $periodText . "
- Zählpunkte " . $meterText . "
- |
-
- Rechnungsadresse
- " . $this->h($user['name'] ?? '') . "
- " . $this->h($user['address'] ?? '') . "
- " . $this->h($user['city'] ?? '') . "
- |
-
-
-
";
+ $y = $pdf->GetY();
+ $pdf->SetFillColor(248, 249, 250);
+ $pdf->Rect(15, $y, 88, 32, 'DF');
+ $pdf->Rect(107, $y, 88, 32, 'D');
+ $pdf->SetFont('dejavusans', 'B', 8);
+ $pdf->SetXY(18, $y + 3);
+ $pdf->MultiCell(82, 4, "Abrechnungszeitraum", 0, 'L');
+ $pdf->SetFont('dejavusans', '', 8);
+ $pdf->SetX(18);
+ $pdf->MultiCell(82, 4, $periodText, 0, 'L');
+ $pdf->SetFont('dejavusans', 'B', 8);
+ $pdf->SetX(18);
+ $pdf->MultiCell(82, 4, "Zählpunkte", 0, 'L');
+ $pdf->SetFont('dejavusans', '', 8);
+ $pdf->SetX(18);
+ $pdf->MultiCell(82, 4, $meterText, 0, 'L');
- // ========================= Elektrizität =========================
- $html .= "Elektrizität
";
+ $pdf->SetFont('dejavusans', 'B', 8);
+ $pdf->SetXY(110, $y + 3);
+ $pdf->MultiCell(82, 4, "Rechnungsadresse", 0, 'L');
+ $pdf->SetFont('dejavusans', '', 8);
+ $pdf->SetX(110);
+ $pdf->MultiCell(82, 4, $addressText, 0, 'L');
+ $pdf->SetY($y + 38);
+ $this->PdfSectionTitle($pdf, 'Elektrizität');
$powerResult = $this->GetCalculatedPowerCosts($user['id']);
- $html .= $powerResult['html'];
+ $this->WritePowerPdfTables($pdf, $powerResult);
$totalPower = $powerResult['sum'];
$appliedTariffs = $this->CollectTariffsForUser($tariffs, ['Netztarif', 'Solartarif', 'Einspeisetarif']);
- if (!empty($appliedTariffs)) {
- $html .= "Angewendete Elektrizitätstarife
";
- foreach ($appliedTariffs as $t) {
- if ($t['start'] === null || $t['end'] === null) {
- continue;
- }
- $html .= "- "
- . $this->h($t['unit_type'] ?? '') . ': '
- . date('d.m.Y', $t['start']) . ' - ' . date('d.m.Y', $t['end'])
- . ' - ' . number_format($t['price'], 3, '.', "'") . ' Rp/kWh'
- . "
";
- }
- $html .= "
";
- }
-
- // ========================= Nebenkosten =========================
- $html .= "Nebenkosten
";
+ $this->WriteTariffPdfTable($pdf, 'Angewendete Elektrizitätstarife', $appliedTariffs, 'Rp/kWh');
+ $this->PdfSectionTitle($pdf, 'Nebenkosten');
$additionalResult = $this->CalculateAdditionalCosts($water, $tariffs, $user['id'], $from, $to);
- $html .= $additionalResult['html'];
+ $this->WriteAdditionalPdfTable($pdf, $additionalResult);
$totalAdditional = $additionalResult['sum'];
+ $this->WriteTariffPdfTable($pdf, 'Angewendete Nebenkostentarife', $additionalResult['tariffs'], 'Rp/Einheit');
- // ========================= Gesamttotal & MWST =========================
$subTotal = $totalPower + $totalAdditional;
$enableVAT = $this->ReadPropertyBoolean('EnableVAT');
-
- $html .= "
-
- | Zusammenfassung |
-
-
- | Zwischentotal Elektrizität |
- " . $this->FormatCurrency($totalPower) . " |
-
-
- | Zwischentotal Nebenkosten |
- " . $this->FormatCurrency($totalAdditional) . " |
-
";
+ $summaryRows = [
+ ['Zwischentotal Elektrizität', $this->FormatCurrency($totalPower)],
+ ['Zwischentotal Nebenkosten', $this->FormatCurrency($totalAdditional)]
+ ];
if ($enableVAT) {
$vatPercent = $this->ReadPropertyFloat('VATPercentage');
$vatAmount = $subTotal * ($vatPercent / 100);
$grandTotal = $subTotal + $vatAmount;
-
- $html .= "
-
- | Total exkl. MWST |
- " . $this->FormatCurrency($subTotal) . " |
-
-
- | MWST " . number_format($vatPercent, 1, '.', "'") . " % |
- " . $this->FormatCurrency($vatAmount) . " |
-
-
- | Gesamttotal inkl. MWST |
- " . $this->FormatCurrency($grandTotal) . " |
-
-
";
+ $summaryRows[] = ['Total exkl. MWST', $this->FormatCurrency($subTotal)];
+ $summaryRows[] = ['MWST ' . number_format($vatPercent, 1, '.', "'") . ' %', $this->FormatCurrency($vatAmount)];
+ $summaryRows[] = ['Gesamttotal inkl. MWST', $this->FormatCurrency($grandTotal)];
} else {
$grandTotal = $subTotal;
- $html .= "
-
- | Gesamttotal |
- " . $this->FormatCurrency($grandTotal) . " |
-
-
- | Nicht mehrwertsteuerpflichtig. |
-
- ";
+ $summaryRows[] = ['Gesamttotal', $this->FormatCurrency($grandTotal)];
}
- $pdf->writeHTML($html, true, false, true, false, '');
+ $this->WriteSummaryPdfTable($pdf, $summaryRows);
+
+ if (!$enableVAT) {
+ $pdf->SetFont('dejavusans', '', 7.5);
+ $pdf->SetTextColor(80, 80, 80);
+ $pdf->Cell(0, 5, 'Nicht mehrwertsteuerpflichtig.', 0, 1, 'L');
+ $pdf->SetTextColor(20, 20, 20);
+ }
return $grandTotal;
}
+
+ private function PdfSectionTitle($pdf, $title)
+ {
+ $this->PdfEnsureSpace($pdf, 12);
+ $pdf->Ln(2);
+ $pdf->SetFont('dejavusans', 'B', 11);
+ $pdf->SetFillColor(235, 238, 242);
+ $pdf->Cell(0, 7, $title, 0, 1, 'L', true);
+ $pdf->Ln(1);
+ }
+
+ private function PdfEnsureSpace($pdf, $height)
+ {
+ if ($pdf->GetY() + $height > 276) {
+ $pdf->AddPage();
+ $pdf->SetY(38);
+ }
+ }
+
+ private function PdfTableHeader($pdf, array $columns)
+ {
+ $this->PdfEnsureSpace($pdf, 8);
+ $pdf->SetFont('dejavusans', 'B', 7.2);
+ $pdf->SetFillColor(231, 234, 238);
+ $values = array_map(function ($column) {
+ return $column['label'];
+ }, $columns);
+ $this->PdfTableRow($pdf, $columns, $values, true, true);
+ }
+
+ private function PdfTableRow($pdf, array $columns, array $values, $bold = false, $fill = false)
+ {
+ $pdf->SetFont('dejavusans', $bold ? 'B' : '', 7.2);
+ $lineHeights = [];
+ foreach ($columns as $index => $column) {
+ $lineHeights[] = $pdf->getStringHeight($column['width'] - 2, (string)($values[$index] ?? '')) + 2;
+ }
+ $height = max(5.5, max($lineHeights));
+ $this->PdfEnsureSpace($pdf, $height + 1);
+
+ $x = 15;
+ $y = $pdf->GetY();
+ $pdf->SetFillColor($fill ? 245 : 255, $fill ? 246 : 255, $fill ? 248 : 255);
+
+ foreach ($columns as $index => $column) {
+ $align = $column['align'] ?? 'L';
+ $pdf->MultiCell(
+ $column['width'],
+ $height,
+ (string)($values[$index] ?? ''),
+ 1,
+ $align,
+ $fill,
+ 0,
+ $x,
+ $y,
+ true,
+ 0,
+ false,
+ true,
+ $height,
+ 'M'
+ );
+ $x += $column['width'];
+ }
+ $pdf->SetY($y + $height);
+ }
+
+ private function WritePowerPdfTables($pdf, array $result)
+ {
+ $rows = $result['rows'];
+ if (empty($rows)) {
+ $this->PdfTableRow($pdf, [['width' => 180, 'align' => 'C']], ['Keine Stromzähler für diesen Benutzer.']);
+ return;
+ }
+
+ $energyColumns = [
+ ['width' => 48, 'label' => 'Zählpunkt'],
+ ['width' => 30, 'label' => 'Import kWh', 'align' => 'R'],
+ ['width' => 30, 'label' => 'Export kWh', 'align' => 'R'],
+ ['width' => 36, 'label' => 'Solarbezug kWh', 'align' => 'R'],
+ ['width' => 36, 'label' => 'Netzbezug kWh', 'align' => 'R']
+ ];
+ $this->PdfTableHeader($pdf, $energyColumns);
+ foreach ($rows as $row) {
+ $this->PdfTableRow($pdf, $energyColumns, [
+ $row['name'],
+ $this->FormatNumber($row['imp'], 3),
+ $this->FormatNumber($row['exp'], 3),
+ $this->FormatNumber($row['solar_bezug'], 3),
+ $this->FormatNumber($row['netz_bezug'], 3)
+ ]);
+ }
+ $this->PdfTableRow($pdf, $energyColumns, [
+ 'Total',
+ $this->FormatNumber($result['totals']['imp'], 3),
+ $this->FormatNumber($result['totals']['exp'], 3),
+ $this->FormatNumber($result['totals']['solar_bezug'], 3),
+ $this->FormatNumber($result['totals']['netz_bezug'], 3)
+ ], true, true);
+
+ $pdf->Ln(2);
+ $exportColumns = [
+ ['width' => 60, 'label' => 'Zählpunkt'],
+ ['width' => 60, 'label' => 'Einspeisung Netz kWh', 'align' => 'R'],
+ ['width' => 60, 'label' => 'ZEV-Verkauf kWh', 'align' => 'R']
+ ];
+ $this->PdfTableHeader($pdf, $exportColumns);
+ foreach ($rows as $row) {
+ $this->PdfTableRow($pdf, $exportColumns, [
+ $row['name'],
+ $this->FormatNumber($row['solareinspeisung'], 3),
+ $this->FormatNumber($row['solarverkauf'], 3)
+ ]);
+ }
+ $this->PdfTableRow($pdf, $exportColumns, [
+ 'Total',
+ $this->FormatNumber($result['totals']['solareinspeisung'], 3),
+ $this->FormatNumber($result['totals']['solarverkauf'], 3)
+ ], true, true);
+
+ $pdf->Ln(2);
+ $amountColumns = [
+ ['width' => 42, 'label' => 'Zählpunkt'],
+ ['width' => 25, 'label' => 'Solar CHF', 'align' => 'R'],
+ ['width' => 25, 'label' => 'Netz CHF', 'align' => 'R'],
+ ['width' => 31, 'label' => 'Gutschrift Netz', 'align' => 'R'],
+ ['width' => 31, 'label' => 'Gutschrift ZEV', 'align' => 'R'],
+ ['width' => 26, 'label' => 'Total CHF', 'align' => 'R']
+ ];
+ $this->PdfTableHeader($pdf, $amountColumns);
+ foreach ($rows as $row) {
+ $this->PdfTableRow($pdf, $amountColumns, [
+ $row['name'],
+ $this->FormatCurrency($row['cost_solar'], false),
+ $this->FormatCurrency($row['cost_grid'], false),
+ $this->FormatCurrency($row['rev_feedin'], false),
+ $this->FormatCurrency($row['rev_zev'], false),
+ $this->FormatCurrency($row['subtotal'], false)
+ ]);
+ }
+ $this->PdfTableRow($pdf, $amountColumns, [
+ 'Zwischentotal Elektrizität', '', '', '', '', $this->FormatCurrency($result['sum'], false)
+ ], true, true);
+ }
+
+ private function WriteAdditionalPdfTable($pdf, array $result)
+ {
+ $columns = [
+ ['width' => 30, 'label' => 'Zähler'],
+ ['width' => 20, 'label' => 'Typ'],
+ ['width' => 30, 'label' => 'Zeitraum'],
+ ['width' => 20, 'label' => 'Start', 'align' => 'R'],
+ ['width' => 20, 'label' => 'Ende', 'align' => 'R'],
+ ['width' => 20, 'label' => 'Verbrauch', 'align' => 'R'],
+ ['width' => 18, 'label' => 'Tarif', 'align' => 'R'],
+ ['width' => 22, 'label' => 'Kosten CHF', 'align' => 'R']
+ ];
+
+ $this->PdfTableHeader($pdf, $columns);
+ if (empty($result['rows'])) {
+ $this->PdfTableRow($pdf, [['width' => 180, 'align' => 'C']], ['Keine Nebenkostenzähler für diesen Benutzer.']);
+ } else {
+ foreach ($result['rows'] as $row) {
+ $this->PdfTableRow($pdf, $columns, [
+ $row['name'],
+ $row['type'],
+ date('d.m.Y', $row['from']) . "\n" . date('d.m.Y', $row['to']),
+ $this->FormatNumber($row['start_value'], 2),
+ $this->FormatNumber($row['end_value'], 2),
+ $this->FormatNumber($row['consumption'], 2),
+ number_format($row['tariff_price'], 3, '.', "'"),
+ $this->FormatCurrency($row['cost'], false)
+ ]);
+ }
+ }
+
+ $this->PdfTableRow($pdf, $columns, [
+ 'Zwischentotal Nebenkosten', '', '', '', '', '', '', $this->FormatCurrency($result['sum'], false)
+ ], true, true);
+ }
+
+ private function WriteTariffPdfTable($pdf, $title, array $tariffs, $unitLabel)
+ {
+ if (empty($tariffs)) {
+ return;
+ }
+
+ $pdf->Ln(2);
+ $pdf->SetFont('dejavusans', 'B', 8);
+ $pdf->Cell(0, 5, $title, 0, 1, 'L');
+ $columns = [
+ ['width' => 48, 'label' => 'Tarifart'],
+ ['width' => 38, 'label' => 'Von'],
+ ['width' => 38, 'label' => 'Bis'],
+ ['width' => 32, 'label' => 'Preis', 'align' => 'R'],
+ ['width' => 24, 'label' => 'Einheit']
+ ];
+ $this->PdfTableHeader($pdf, $columns);
+ foreach ($tariffs as $tariff) {
+ if (($tariff['start'] ?? null) === null || ($tariff['end'] ?? null) === null) {
+ continue;
+ }
+ $this->PdfTableRow($pdf, $columns, [
+ $tariff['unit_type'] ?? '',
+ date('d.m.Y', $tariff['start']),
+ date('d.m.Y', $tariff['end']),
+ number_format((float)($tariff['price'] ?? 0), 3, '.', "'"),
+ $unitLabel
+ ]);
+ }
+ }
+
+ private function WriteSummaryPdfTable($pdf, array $rows)
+ {
+ $this->PdfSectionTitle($pdf, 'Zusammenfassung');
+ $columns = [
+ ['width' => 130, 'label' => 'Position'],
+ ['width' => 50, 'label' => 'Betrag', 'align' => 'R']
+ ];
+ foreach ($rows as $index => $row) {
+ $isTotal = $index === count($rows) - 1;
+ $this->PdfTableRow($pdf, $columns, [$row[0], $row[1]], $isTotal, $isTotal);
+ }
+ }
// ====================== QR-Einzahlungsschein (Schweiz) ======================
private function BuildQRBill($pdf, $user, $amount)
@@ -918,16 +1275,6 @@ public function GenerateInvoices()
}
private function GetCalculatedPowerCosts($userId)
{
- if (empty($this->powerCostCache) || !isset($this->powerCostCache[$userId])) {
- return [
- 'html' => "| Keine Stromzähler für diesen Benutzer |
",
- 'sum' => 0.0
- ];
- }
-
- $energyRows = '';
- $amountRows = '';
- $sum = 0.0;
$totals = [
'imp' => 0.0,
'exp' => 0.0,
@@ -940,7 +1287,12 @@ public function GenerateInvoices()
'rev_feedin' => 0.0,
'rev_zev' => 0.0
];
- $rowIndex = 0;
+ $rows = [];
+ $sum = 0.0;
+
+ if (empty($this->powerCostCache) || !isset($this->powerCostCache[$userId])) {
+ return ['rows' => [], 'totals' => $totals, 'sum' => 0.0];
+ }
foreach ($this->powerCostCache[$userId] as $name => $a) {
$subtotal = $a['cost_grid'] + $a['cost_solar'] - ($a['rev_feedin'] + $a['rev_zev']);
@@ -950,90 +1302,31 @@ public function GenerateInvoices()
$totals[$key] += (float)($a[$key] ?? 0.0);
}
- $rowStyle = ($rowIndex % 2 === 0) ? " style='background-color:#fbfbfb;'" : '';
- $rowIndex++;
- $label = $this->h($a['name'] ?? $name);
-
- $energyRows .= "
- | " . $label . " |
- " . $this->FormatNumber($a['imp'], 3) . " |
- " . $this->FormatNumber($a['exp'], 3) . " |
- " . $this->FormatNumber($a['solar_bezug'], 3) . " |
- " . $this->FormatNumber($a['netz_bezug'], 3) . " |
- " . $this->FormatNumber($a['solareinspeisung'], 3) . " |
- " . $this->FormatNumber($a['solarverkauf'], 3) . " |
-
";
-
- $amountRows .= "
- | " . $label . " |
- " . $this->FormatCurrency($a['cost_solar'], false) . " |
- " . $this->FormatCurrency($a['cost_grid'], false) . " |
- " . $this->FormatCurrency($a['rev_feedin'], false) . " |
- " . $this->FormatCurrency($a['rev_zev'], false) . " |
- " . $this->FormatCurrency($subtotal, false) . " |
-
";
+ $rows[] = [
+ 'name' => (string)($a['name'] ?? $name),
+ 'imp' => (float)$a['imp'],
+ 'exp' => (float)$a['exp'],
+ 'solar_bezug' => (float)$a['solar_bezug'],
+ 'netz_bezug' => (float)$a['netz_bezug'],
+ 'solareinspeisung' => (float)$a['solareinspeisung'],
+ 'solarverkauf' => (float)$a['solarverkauf'],
+ 'cost_solar' => (float)$a['cost_solar'],
+ 'cost_grid' => (float)$a['cost_grid'],
+ 'rev_feedin' => (float)$a['rev_feedin'],
+ 'rev_zev' => (float)$a['rev_zev'],
+ 'subtotal' => (float)$subtotal
+ ];
}
- $html = "
-
-
- | Zählpunkt |
- Import kWh |
- Export kWh |
- Solarbezug kWh |
- Netzbezug kWh |
- Einspeisung kWh |
- ZEV-Verkauf kWh |
-
" . $energyRows . "
-
- | Total |
- " . $this->FormatNumber($totals['imp'], 3) . " |
- " . $this->FormatNumber($totals['exp'], 3) . " |
- " . $this->FormatNumber($totals['solar_bezug'], 3) . " |
- " . $this->FormatNumber($totals['netz_bezug'], 3) . " |
- " . $this->FormatNumber($totals['solareinspeisung'], 3) . " |
- " . $this->FormatNumber($totals['solarverkauf'], 3) . " |
-
-
-
-
-
- | Zählpunkt |
- Kauf Solar CHF |
- Kauf Netz CHF |
- Gutschrift Netz CHF |
- Gutschrift ZEV CHF |
- Total CHF |
-
" . $amountRows . "
-
- | Zwischentotal Elektrizität |
- " . $this->FormatCurrency($sum, false) . " |
-
-
";
-
- return ['html' => $html, 'sum' => $sum];
+ return ['rows' => $rows, 'totals' => $totals, 'sum' => $sum];
}
// ====================== Nebenkosten Wasser/Wärme ======================
private function CalculateAdditionalCosts($waterMeters, $tariffs, $userId, $from, $to)
{
- $html = "
-
-
- | Zähler |
- Typ |
- Von |
- Bis |
- Start |
- Ende |
- Verbrauch |
- Tarif |
- Kosten CHF |
-
";
-
+ $rows = [];
$total = 0.0;
$usedTariffs = [];
- $hasRows = false;
foreach ($waterMeters as $m) {
if (($m['user_id'] ?? null) != $userId) {
@@ -1041,46 +1334,21 @@ public function GenerateInvoices()
}
$type = $m['meter_type'] ?? 'Warmwasser';
$cost = $this->AddMeterToPDFRow($m, $tariffs, $from, $to, $type);
- if ($cost['row'] !== '') {
- $hasRows = true;
+ if (!empty($cost['row'])) {
+ $rows[] = $cost['row'];
}
- $html .= $cost['row'];
$total += $cost['value'];
$usedTariffs = array_merge($usedTariffs, $cost['tariffs']);
}
- if (!$hasRows) {
- $html .= "| Keine Nebenkostenzähler für diesen Benutzer |
";
- }
-
- $html .= "
-
- | Zwischentotal Nebenkosten |
- " . $this->FormatCurrency($total) . " |
-
-
";
-
- if (!empty($usedTariffs)) {
- $html .= "Angewendete Nebenkostentarife
";
- foreach ($usedTariffs as $t) {
- if ($t['start'] === null || $t['end'] === null) {
- continue;
- }
- $html .= "- " . $this->h($t['unit_type'] ?? '') . ': '
- . date('d.m.Y', $t['start']) . ' - ' . date('d.m.Y', $t['end'])
- . ' - ' . number_format($t['price'], 3, '.', "'") . " Rp/Einheit
";
- }
- $html .= "
";
- }
-
- return ['html' => $html, 'sum' => $total];
+ return ['rows' => $rows, 'sum' => $total, 'tariffs' => $usedTariffs];
}
private function AddMeterToPDFRow($meter, $tariffs, $from, $to, $type)
{
$varId = (int)($meter['var_consumption'] ?? 0);
if ($varId <= 0 || !IPS_VariableExists($varId)) {
- return ['row' => '', 'value' => 0, 'tariffs' => []];
+ return ['row' => null, 'value' => 0, 'tariffs' => []];
}
$filteredTariffs = array_filter($tariffs, function ($t) use ($type) {
@@ -1113,25 +1381,27 @@ public function GenerateInvoices()
$endValue = $this->GetValueAt($varId, $to, false);
if ($startValue === null || $endValue === null) {
- return ['row' => '', 'value' => 0, 'tariffs' => []];
+ return ['row' => null, 'value' => 0, 'tariffs' => []];
}
$verbrauch = max(0, $endValue - $startValue);
$kosten = round(($tariffPrice / 100) * $verbrauch, 2);
- $row = "
- | " . $this->h($meter['name'] ?? '') . " |
- " . $this->h($type) . " |
- " . date('d.m.Y', $from) . " |
- " . date('d.m.Y', $to) . " |
- " . $this->FormatNumber($startValue, 2) . " |
- " . $this->FormatNumber($endValue, 2) . " |
- " . $this->FormatNumber($verbrauch, 2) . " |
- " . number_format($tariffPrice, 3, '.', "'") . " |
- " . $this->FormatCurrency($kosten, false) . " |
-
";
-
- return ['row' => $row, 'value' => $kosten, 'tariffs' => array_values($usedTariffs)];
+ return [
+ 'row' => [
+ 'name' => (string)($meter['name'] ?? ''),
+ 'type' => (string)$type,
+ 'from' => (int)$from,
+ 'to' => (int)$to,
+ 'start_value' => (float)$startValue,
+ 'end_value' => (float)$endValue,
+ 'consumption' => (float)$verbrauch,
+ 'tariff_price' => (float)$tariffPrice,
+ 'cost' => (float)$kosten
+ ],
+ 'value' => $kosten,
+ 'tariffs' => array_values($usedTariffs)
+ ];
}
// ====================== Hilfsfunktionen ======================