no message

This commit is contained in:
2026-03-19 16:44:55 +01:00
parent a57a76972d
commit 724f3b0d2a
2 changed files with 406 additions and 318 deletions

View File

@@ -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" }
]
}
}
]
}
]
}
}

View File

@@ -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);
@@ -851,4 +944,4 @@ private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
return $path;
}
}
?>
?>