no message
This commit is contained in:
@@ -19,121 +19,116 @@
|
||||
"name": "FooterText",
|
||||
"caption": "Fusszeile"
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"type": "List",
|
||||
"name": "Users",
|
||||
"caption": "Benutzerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Adresse", "name": "address", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Ort", "name": "city", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||
]
|
||||
|
||||
"type": "Label",
|
||||
"caption": "🏦 QR-Einzahlungsschein Daten"
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"type": "List",
|
||||
"name": "PowerMeters",
|
||||
"caption": "Stromzählerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Var. Verbrauch", "name": "var_consumption", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Var. Rückspeisung", "name": "var_feed", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||
]
|
||||
"type": "ValidationTextBox",
|
||||
"name": "CreditorName",
|
||||
"caption": "Rechnungssteller Name"
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"type": "List",
|
||||
"name": "WaterMeters",
|
||||
"caption": "Verbrauchszählerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Var. Verbrauch", "name": "var_consumption", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{
|
||||
"caption": "Zählertyp",
|
||||
"name": "meter_type",
|
||||
"width": "20%",
|
||||
"add": "Warmwasser",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"options": [
|
||||
{ "caption": "Warmwasser", "value": "Warmwasser" },
|
||||
{ "caption": "Kaltwasser", "value": "Kaltwasser" },
|
||||
{ "caption": "Wärme", "value": "Wärme" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
"type": "ValidationTextBox",
|
||||
"name": "CreditorAddress",
|
||||
"caption": "Rechnungssteller Strasse/Nr."
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "CreditorCity",
|
||||
"caption": "Rechnungssteller PLZ/Ort"
|
||||
},
|
||||
{
|
||||
"type": "ValidationTextBox",
|
||||
"name": "BankIBAN",
|
||||
"caption": "Bank IBAN"
|
||||
},
|
||||
{
|
||||
"type": "List",
|
||||
"name": "Users",
|
||||
"caption": "Benutzerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Adresse", "name": "address", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Ort", "name": "city", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
|
||||
"type": "List",
|
||||
"name": "Tariffs",
|
||||
"caption": "Tarifübersicht",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{
|
||||
"caption": "Startdatum",
|
||||
"name": "start",
|
||||
"width": "20%",
|
||||
"add": "",
|
||||
"edit": { "type": "SelectDate" }
|
||||
},
|
||||
{
|
||||
"caption": "Enddatum",
|
||||
"name": "end",
|
||||
"width": "20%",
|
||||
"add": "",
|
||||
"edit": { "type": "SelectDate" }
|
||||
},
|
||||
{
|
||||
"caption": "Tarif (Rp/Einheit)",
|
||||
"name": "price",
|
||||
"width": "20%",
|
||||
"add": 0,
|
||||
"edit": { "type": "NumberSpinner", "digits": 3, "minimum": 0 }
|
||||
},
|
||||
{
|
||||
"caption": "Einheit",
|
||||
"name": "unit_type",
|
||||
"width": "20%",
|
||||
"add": "Netztarif",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"options": [
|
||||
{ "caption": "Netztarif", "value": "Netztarif" },
|
||||
{ "caption": "Einspeisetarif", "value": "Einspeisetarif" },
|
||||
{ "caption": "Solartarif", "value": "Solartarif" },
|
||||
{ "caption": "Warmwasser", "value": "Warmwasser" },
|
||||
{ "caption": "Kaltwasser", "value": "Kaltwasser" },
|
||||
{ "caption": "Wärme", "value": "Wärme" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
"type": "List",
|
||||
"name": "PowerMeters",
|
||||
"caption": "Stromzählerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Var. Verbrauch", "name": "var_consumption", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Var. Rückspeisung", "name": "var_feed", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "List",
|
||||
"name": "WaterMeters",
|
||||
"caption": "Verbrauchszählerliste",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "ID", "name": "id", "width": "10%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Name", "name": "name", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{ "caption": "Var. Verbrauch", "name": "var_consumption", "width": "20%", "add": 0, "edit": { "type": "SelectVariable" } },
|
||||
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } },
|
||||
{
|
||||
"caption": "Zählertyp",
|
||||
"name": "meter_type",
|
||||
"width": "20%",
|
||||
"add": "Warmwasser",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"options": [
|
||||
{ "caption": "Warmwasser", "value": "Warmwasser" },
|
||||
{ "caption": "Kaltwasser", "value": "Kaltwasser" },
|
||||
{ "caption": "Wärme", "value": "Wärme" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "List",
|
||||
"name": "Tariffs",
|
||||
"caption": "Tarifübersicht",
|
||||
"add": true,
|
||||
"delete": true,
|
||||
"sortable": true,
|
||||
"columns": [
|
||||
{ "caption": "Startdatum", "name": "start", "width": "20%", "add": "", "edit": { "type": "SelectDate" } },
|
||||
{ "caption": "Enddatum", "name": "end", "width": "20%", "add": "", "edit": { "type": "SelectDate" } },
|
||||
{ "caption": "Tarif (Rp/Einheit)", "name": "price", "width": "20%", "add": 0, "edit": { "type": "NumberSpinner", "digits": 3, "minimum": 0 } },
|
||||
{
|
||||
"caption": "Einheit",
|
||||
"name": "unit_type",
|
||||
"width": "20%",
|
||||
"add": "Netztarif",
|
||||
"edit": {
|
||||
"type": "Select",
|
||||
"options": [
|
||||
{ "caption": "Netztarif", "value": "Netztarif" },
|
||||
{ "caption": "Einspeisetarif", "value": "Einspeisetarif" },
|
||||
{ "caption": "Solartarif", "value": "Solartarif" },
|
||||
{ "caption": "Warmwasser", "value": "Warmwasser" },
|
||||
{ "caption": "Kaltwasser", "value": "Kaltwasser" },
|
||||
{ "caption": "Wärme", "value": "Wärme" }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -44,6 +44,12 @@ class Abrechnung extends IPSModule
|
||||
$this->RegisterPropertyString('PropertyText', 'Liegenschaft');
|
||||
$this->RegisterPropertyString('FooterText', 'Belevo AG • 6122 Menznau • www.belevo.ch');
|
||||
|
||||
// Neue Felder für den QR-Schein
|
||||
$this->RegisterPropertyString('CreditorName', 'Belevo AG');
|
||||
$this->RegisterPropertyString('CreditorAddress', 'Musterstrasse 1');
|
||||
$this->RegisterPropertyString('CreditorCity', '6122 Menznau');
|
||||
$this->RegisterPropertyString('BankIBAN', '');
|
||||
|
||||
$this->RegisterVariableInteger('FromDate', 'Startdatum', '~UnixTimestamp', 1);
|
||||
$this->RegisterVariableInteger('ToDate', 'Enddatum', '~UnixTimestamp', 2);
|
||||
$this->RegisterVariableString('LastResult', 'Letzte Abrechnung', '', 3);
|
||||
@@ -139,7 +145,11 @@ class Abrechnung extends IPSModule
|
||||
|
||||
foreach ($users as $user) {
|
||||
$pdf->AddPage();
|
||||
$this->BuildUserInvoice($pdf, $user, $power, $water, $tariffs, $from, $to);
|
||||
// Hole das berechnete Total aus der Rechnung
|
||||
$grandTotal = $this->BuildUserInvoice($pdf, $user, $power, $water, $tariffs, $from, $to);
|
||||
|
||||
// Generiere den Einzahlungsschein, wenn eine IBAN vorhanden ist
|
||||
$this->BuildQRBill($pdf, $user, $grandTotal);
|
||||
}
|
||||
|
||||
return $pdf->Output('Abrechnung.pdf', 'S');
|
||||
@@ -223,6 +233,136 @@ class Abrechnung extends IPSModule
|
||||
</h2>
|
||||
";
|
||||
$pdf->writeHTML($html, true, false, true, false, '');
|
||||
|
||||
// WICHTIG: Total zurückgeben für den Einzahlungsschein
|
||||
return $grandTotal;
|
||||
}
|
||||
|
||||
// ====================== QR-Einzahlungsschein (Schweiz) ======================
|
||||
|
||||
private function BuildQRBill($pdf, $user, $amount)
|
||||
{
|
||||
$iban = str_replace(' ', '', $this->ReadPropertyString('BankIBAN'));
|
||||
|
||||
// Kein Einzahlungsschein, wenn IBAN leer ist oder Betrag <= 0
|
||||
if (empty($iban) || $amount <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pdf->AddPage();
|
||||
$pdf->SetAutoPageBreak(false);
|
||||
|
||||
// Position des Einzahlungsscheins unten auf A4 (Höhe 105mm)
|
||||
$yStart = 192;
|
||||
|
||||
// Trennlinien zeichnen
|
||||
$pdf->SetLineStyle(['dash' => '2,2', 'color' => [0, 0, 0]]);
|
||||
$pdf->Line(0, $yStart, 210, $yStart); // Horizontal
|
||||
$pdf->Line(62, $yStart, 62, 297); // Vertikal
|
||||
$pdf->SetLineStyle(['dash' => 0, 'color' => [0, 0, 0]]); // Reset
|
||||
|
||||
$creditorName = $this->ReadPropertyString('CreditorName');
|
||||
$creditorCity = $this->ReadPropertyString('CreditorCity');
|
||||
$bankIBAN_formatted = wordwrap($iban, 4, ' ', true);
|
||||
|
||||
// --- LINKER TEIL: Empfangsschein ---
|
||||
$pdf->SetFont('dejavusans', 'B', 11);
|
||||
$pdf->SetXY(5, $yStart + 5);
|
||||
$pdf->Cell(52, 5, 'Empfangsschein', 0, 1);
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 6);
|
||||
$pdf->SetXY(5, $yStart + 12);
|
||||
$pdf->Cell(52, 3, 'Konto / Zahlbar an', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $bankIBAN_formatted, 0, 1);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $creditorName, 0, 1);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $creditorCity, 0, 1);
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 6);
|
||||
$pdf->SetXY(5, $yStart + 35);
|
||||
$pdf->Cell(52, 3, 'Zahlbar durch', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 6);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $user['name'] ?? '', 0, 1);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $user['address'] ?? '', 0, 1);
|
||||
$pdf->SetX(5); $pdf->Cell(52, 3, $user['city'] ?? '', 0, 1);
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 6);
|
||||
$pdf->SetXY(5, $yStart + 60);
|
||||
$pdf->Cell(15, 3, 'Währung', 0, 0);
|
||||
$pdf->Cell(20, 3, 'Betrag', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetX(5);
|
||||
$pdf->Cell(15, 4, 'CHF', 0, 0);
|
||||
$pdf->Cell(20, 4, number_format($amount, 2, '.', ''), 0, 1);
|
||||
|
||||
// --- RECHTER TEIL: Zahlteil ---
|
||||
$pdf->SetFont('dejavusans', 'B', 11);
|
||||
$pdf->SetXY(118, $yStart + 5);
|
||||
$pdf->Cell(50, 5, 'Zahlteil', 0, 1);
|
||||
|
||||
// QR Code String generieren & rendern (46x46mm)
|
||||
$qrString = $this->GenerateQRString($iban, $amount, $user);
|
||||
$style = ['border' => false, 'padding' => 0, 'fgcolor' => [0, 0, 0], 'bgcolor' => false];
|
||||
$pdf->write2DBarcode($qrString, 'QRCODE,M', 67, $yStart + 17, 46, 46, $style, 'N');
|
||||
|
||||
// Schweizer Kreuz über den QR Code zeichnen (7x7mm Block)
|
||||
$pdf->SetFillColor(0, 0, 0);
|
||||
$pdf->Rect(67 + 19.5, $yStart + 17 + 19.5, 7, 7, 'F');
|
||||
$pdf->SetFillColor(255, 255, 255);
|
||||
$pdf->Rect(67 + 19.5 + 3, $yStart + 17 + 19.5 + 1, 1, 5, 'F');
|
||||
$pdf->Rect(67 + 19.5 + 1, $yStart + 17 + 19.5 + 3, 5, 1, 'F');
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$pdf->SetXY(118, $yStart + 17);
|
||||
$pdf->Cell(80, 4, 'Konto / Zahlbar an', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $bankIBAN_formatted, 0, 1);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $creditorName, 0, 1);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $creditorCity, 0, 1);
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$pdf->SetXY(118, $yStart + 40);
|
||||
$pdf->Cell(80, 4, 'Zahlbar durch', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 8);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $user['name'] ?? '', 0, 1);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $user['address'] ?? '', 0, 1);
|
||||
$pdf->SetX(118); $pdf->Cell(80, 4, $user['city'] ?? '', 0, 1);
|
||||
|
||||
$pdf->SetFont('dejavusans', 'B', 8);
|
||||
$pdf->SetXY(67, $yStart + 68);
|
||||
$pdf->Cell(15, 4, 'Währung', 0, 0);
|
||||
$pdf->Cell(20, 4, 'Betrag', 0, 1);
|
||||
$pdf->SetFont('dejavusans', '', 10);
|
||||
$pdf->SetX(67);
|
||||
$pdf->Cell(15, 5, 'CHF', 0, 0);
|
||||
$pdf->Cell(20, 5, number_format($amount, 2, '.', ''), 0, 1);
|
||||
}
|
||||
|
||||
private function GenerateQRString($iban, $amount, $user)
|
||||
{
|
||||
// Daten nach SIX Swiss Payment Standard beschneiden (max 70 Zeichen pro Zeile)
|
||||
$creditorName = mb_substr($this->ReadPropertyString('CreditorName'), 0, 70);
|
||||
$creditorStr = mb_substr($this->ReadPropertyString('CreditorAddress'), 0, 70);
|
||||
$creditorCity = mb_substr($this->ReadPropertyString('CreditorCity'), 0, 70);
|
||||
|
||||
$debtorName = mb_substr($user['name'] ?? '', 0, 70);
|
||||
$debtorStr = mb_substr($user['address'] ?? '', 0, 70);
|
||||
$debtorCity = mb_substr($user['city'] ?? '', 0, 70);
|
||||
|
||||
$amountStr = number_format($amount, 2, '.', '');
|
||||
|
||||
// EPC / Swiss QR Code String Format
|
||||
$lines = [
|
||||
'SPC', '0200', '1', $iban, 'K',
|
||||
$creditorName, $creditorStr, $creditorCity, '', '', 'CH',
|
||||
'', '', '', '', '', '', '', // Ultimate Creditor (leer)
|
||||
$amountStr, 'CHF', 'K',
|
||||
$debtorName, $debtorStr, $debtorCity, '', '', 'CH',
|
||||
'NON', '', // Reference Type (NON = Ohne Referenz)
|
||||
'EPD', 'Abrechnung Liegenschaft'
|
||||
];
|
||||
|
||||
return implode("\r\n", $lines);
|
||||
}
|
||||
|
||||
// ====================== Stromkosten (15-Minuten, alle User) ======================
|
||||
@@ -396,121 +536,121 @@ class Abrechnung extends IPSModule
|
||||
}
|
||||
|
||||
}
|
||||
private function GetCalculatedPowerCosts($userId)
|
||||
{
|
||||
$html = "
|
||||
<style>
|
||||
table.powercost {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10px;
|
||||
}
|
||||
table.powercost th {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
table.powercost td {
|
||||
padding: 3px 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.row-even { background-color: #f7f7f7; }
|
||||
.row-odd { background-color: #ffffff; }
|
||||
.row-empty { background-color: #e0e0e0; height: 5px; }
|
||||
private function GetCalculatedPowerCosts($userId)
|
||||
{
|
||||
$html = "
|
||||
<style>
|
||||
table.powercost {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10px;
|
||||
}
|
||||
table.powercost th {
|
||||
background-color: #444;
|
||||
color: white;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border: 1px solid #777;
|
||||
}
|
||||
table.powercost td {
|
||||
padding: 3px 4px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.row-even { background-color: #f7f7f7; }
|
||||
.row-odd { background-color: #ffffff; }
|
||||
.row-empty { background-color: #e0e0e0; height: 5px; }
|
||||
|
||||
/* Neue Formatierungen */
|
||||
.num {
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
width: 90px;
|
||||
}
|
||||
.subtotal-row {
|
||||
border-top: 1px solid black;
|
||||
font-weight: bold;
|
||||
}
|
||||
.total-row {
|
||||
border-top: 3px double black;
|
||||
font-weight: bold;
|
||||
background-color: #ddd;
|
||||
}
|
||||
</style>
|
||||
/* Neue Formatierungen */
|
||||
.num {
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
width: 90px;
|
||||
}
|
||||
.subtotal-row {
|
||||
border-top: 1px solid black;
|
||||
font-weight: bold;
|
||||
}
|
||||
.total-row {
|
||||
border-top: 3px double black;
|
||||
font-weight: bold;
|
||||
background-color: #ddd;
|
||||
}
|
||||
</style>
|
||||
|
||||
<table class='powercost'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Import (kWh)</th>
|
||||
<th>Export (kWh)</th>
|
||||
<th>ZEV-Haus (kWh)</th>
|
||||
<th>Netz-Haus (kWh)</th>
|
||||
<th>Solar-Netz (kWh)</th>
|
||||
<th>Solar-ZEV (kWh)</th>
|
||||
<th>Kauf Solar (CHF)</th>
|
||||
<th>Kauf Netz (CHF)</th>
|
||||
<th>Verkauf Netz(CHF)</th>
|
||||
<th>Verkauf ZEV(CHF)</th>
|
||||
<th>Total CHF</th>
|
||||
</tr>";
|
||||
<table class='powercost'>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Import (kWh)</th>
|
||||
<th>Export (kWh)</th>
|
||||
<th>ZEV-Haus (kWh)</th>
|
||||
<th>Netz-Haus (kWh)</th>
|
||||
<th>Solar-Netz (kWh)</th>
|
||||
<th>Solar-ZEV (kWh)</th>
|
||||
<th>Kauf Solar (CHF)</th>
|
||||
<th>Kauf Netz (CHF)</th>
|
||||
<th>Verkauf Netz(CHF)</th>
|
||||
<th>Verkauf ZEV(CHF)</th>
|
||||
<th>Total CHF</th>
|
||||
</tr>";
|
||||
|
||||
if (empty($this->powerCostCache) || !isset($this->powerCostCache[$userId])) {
|
||||
$html .= "<tr><td colspan='12' align='center'>Keine Stromzähler für diesen Benutzer</td></tr></table>";
|
||||
return ['html' => $html, 'sum' => 0.0];
|
||||
if (empty($this->powerCostCache) || !isset($this->powerCostCache[$userId])) {
|
||||
$html .= "<tr><td colspan='12' align='center'>Keine Stromzähler für diesen Benutzer</td></tr></table>";
|
||||
return ['html' => $html, 'sum' => 0.0];
|
||||
}
|
||||
|
||||
$sum = 0.0;
|
||||
$rowIndex = 0;
|
||||
|
||||
foreach ($this->powerCostCache[$userId] as $name => $a) {
|
||||
|
||||
$subtotal = $a['cost_grid'] + $a['cost_solar'] - ($a['rev_feedin'] + $a['rev_zev']);
|
||||
$sum += $subtotal;
|
||||
|
||||
$rowClass = ($rowIndex % 2 === 0) ? "row-even" : "row-odd";
|
||||
$rowIndex++;
|
||||
|
||||
// Datenzeile
|
||||
$html .= "<tr class='{$rowClass}'>
|
||||
<td>{$a['name']}</td>
|
||||
<td class='num' style='background-color:#f0f0f0; text-align: right;'>" . number_format($a['imp'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['exp'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solar_bezug'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['netz_bezug'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solareinspeisung'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solarverkauf'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['cost_solar'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['cost_grid'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['rev_feedin'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['rev_zev'], 2, '.', "'") . "</td>
|
||||
<td class='num underline' text-align='right'><strong>" . number_format($subtotal, 2, '.', "'") . "</strong></td>
|
||||
</tr>";
|
||||
|
||||
// Leerzeile
|
||||
$html .= "<tr class='row-empty'><td colspan='12'></td></tr>";
|
||||
}
|
||||
|
||||
// Gesamttotal
|
||||
$html .= "
|
||||
<tr class='total-row'>
|
||||
<td colspan='11' text-align='right'><b>Total</b></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td class='num double-underline' text-align='right'><strong>" . number_format($sum, 2, '.', "'") . "</strong></td>
|
||||
</tr>
|
||||
</table>";
|
||||
|
||||
return ['html' => $html, 'sum' => $sum];
|
||||
}
|
||||
|
||||
$sum = 0.0;
|
||||
$rowIndex = 0;
|
||||
|
||||
foreach ($this->powerCostCache[$userId] as $name => $a) {
|
||||
|
||||
$subtotal = $a['cost_grid'] + $a['cost_solar'] - ($a['rev_feedin'] + $a['rev_zev']);
|
||||
$sum += $subtotal;
|
||||
|
||||
$rowClass = ($rowIndex % 2 === 0) ? "row-even" : "row-odd";
|
||||
$rowIndex++;
|
||||
|
||||
// Datenzeile
|
||||
$html .= "<tr class='{$rowClass}'>
|
||||
<td>{$a['name']}</td>
|
||||
<td class='num' style='background-color:#f0f0f0; text-align: right;'>" . number_format($a['imp'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['exp'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solar_bezug'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['netz_bezug'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solareinspeisung'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['solarverkauf'], 3, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['cost_solar'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['cost_grid'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['rev_feedin'], 2, '.', "'") . "</td>
|
||||
<td class='num' text-align='right'>" . number_format($a['rev_zev'], 2, '.', "'") . "</td>
|
||||
<td class='num underline' text-align='right'><strong>" . number_format($subtotal, 2, '.', "'") . "</strong></td>
|
||||
</tr>";
|
||||
|
||||
// Leerzeile
|
||||
$html .= "<tr class='row-empty'><td colspan='12'></td></tr>";
|
||||
}
|
||||
|
||||
// Gesamttotal
|
||||
$html .= "
|
||||
<tr class='total-row'>
|
||||
<td colspan='11' text-align='right'><b>Total</b></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td align='right'></td>
|
||||
<td class='num double-underline' text-align='right'><strong>" . number_format($sum, 2, '.', "'") . "</strong></td>
|
||||
</tr>
|
||||
</table>";
|
||||
|
||||
return ['html' => $html, 'sum' => $sum];
|
||||
}
|
||||
|
||||
// ====================== Nebenkosten Wasser/Wärme ======================
|
||||
|
||||
private function CalculateAdditionalCosts($waterMeters, $tariffs, $userId, $from, $to)
|
||||
@@ -638,62 +778,61 @@ private function GetCalculatedPowerCosts($userId)
|
||||
|
||||
// ====================== Hilfsfunktionen ======================
|
||||
|
||||
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
|
||||
{
|
||||
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
|
||||
if (!$archiveID || !IPS_VariableExists($varId)) {
|
||||
return null;
|
||||
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
|
||||
{
|
||||
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
|
||||
if (!$archiveID || !IPS_VariableExists($varId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($nearestAfter) {
|
||||
// Erster Wert NACH oder GENAU ab Timestamp
|
||||
$values = @AC_GetLoggedValues(
|
||||
$archiveID,
|
||||
$varId,
|
||||
$timestamp, // start
|
||||
time(), // end
|
||||
1 // LIMIT
|
||||
);
|
||||
} else {
|
||||
// Letzter Wert DAVOR oder GENAU bis Timestamp
|
||||
$values = @AC_GetLoggedValues(
|
||||
$archiveID,
|
||||
$varId,
|
||||
0, // start
|
||||
$timestamp, // end
|
||||
1 // LIMIT
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($values)) {
|
||||
return (float)$values[0]['Value'];
|
||||
}
|
||||
|
||||
// Fallback → Live-Wert
|
||||
return (float)GetValue($varId);
|
||||
}
|
||||
|
||||
if ($nearestAfter) {
|
||||
// Erster Wert NACH oder GENAU ab Timestamp
|
||||
$values = @AC_GetLoggedValues(
|
||||
$archiveID,
|
||||
$varId,
|
||||
$timestamp, // start
|
||||
time(), // end
|
||||
1 // LIMIT
|
||||
);
|
||||
} else {
|
||||
// Letzter Wert DAVOR oder GENAU bis Timestamp
|
||||
$values = @AC_GetLoggedValues(
|
||||
$archiveID,
|
||||
$varId,
|
||||
0, // start
|
||||
$timestamp, // end
|
||||
1 // LIMIT
|
||||
);
|
||||
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
||||
{
|
||||
// Werte holen
|
||||
$startValue = $this->GetValueAt($varId, $tStart, false);
|
||||
$endValue = $this->GetValueAt($varId, $tEnd, false);
|
||||
|
||||
if ($startValue === null || $endValue === null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Delta berechnen
|
||||
$diff = $endValue - $startValue;
|
||||
|
||||
if ($diff < 0) {
|
||||
$diff = 0.0;
|
||||
}
|
||||
|
||||
return (float)$diff;
|
||||
}
|
||||
|
||||
if (!empty($values)) {
|
||||
return (float)$values[0]['Value'];
|
||||
}
|
||||
|
||||
// Fallback → Live-Wert
|
||||
return (float)GetValue($varId);
|
||||
}
|
||||
|
||||
|
||||
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
||||
{
|
||||
// Werte holen
|
||||
$startValue = $this->GetValueAt($varId, $tStart, false);
|
||||
$endValue = $this->GetValueAt($varId, $tEnd, false);
|
||||
|
||||
if ($startValue === null || $endValue === null) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Delta berechnen
|
||||
$diff = $endValue - $startValue;
|
||||
|
||||
if ($diff < 0) {
|
||||
$diff = 0.0;
|
||||
}
|
||||
|
||||
return (float)$diff;
|
||||
}
|
||||
|
||||
private function toUnixTs($val, $endOfDay = false)
|
||||
{
|
||||
if (is_int($val)) {
|
||||
@@ -724,52 +863,6 @@ private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
||||
return null;
|
||||
}
|
||||
|
||||
private function readDelta($varId, $tStart, $tEnd)
|
||||
{
|
||||
if (!is_int($tStart)) {
|
||||
$tStart = strtotime($tStart);
|
||||
}
|
||||
if (!is_int($tEnd)) {
|
||||
$tEnd = strtotime($tEnd);
|
||||
}
|
||||
|
||||
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
|
||||
if (!$archiveID || !IPS_VariableExists($varId)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
$values = @AC_GetLoggedValues($archiveID, $varId, $tStart - 86400, $tEnd + 86400, 0);
|
||||
if (empty($values)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
usort($values, fn($a, $b) => (int)$a['TimeStamp'] <=> (int)$b['TimeStamp']);
|
||||
$vStart = null;
|
||||
$vEnd = null;
|
||||
|
||||
foreach ($values as $v) {
|
||||
if ($v['TimeStamp'] <= $tStart) {
|
||||
$vStart = $v['Value'];
|
||||
}
|
||||
if ($v['TimeStamp'] <= $tEnd) {
|
||||
$vEnd = $v['Value'];
|
||||
}
|
||||
if ($v['TimeStamp'] > $tEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($vStart === null) {
|
||||
$vStart = (float)GetValue($varId);
|
||||
}
|
||||
if ($vEnd === null) {
|
||||
$vEnd = (float)GetValue($varId);
|
||||
}
|
||||
|
||||
$diff = $vEnd - $vStart;
|
||||
return ($diff < 0) ? 0.0 : $diff;
|
||||
}
|
||||
|
||||
private function getTariffPriceAt($tariffs, $typeSynonyms, $ts)
|
||||
{
|
||||
$wanted = array_map('strtolower', $typeSynonyms);
|
||||
|
||||
Reference in New Issue
Block a user