no message
This commit is contained in:
+27
-32
@@ -19,9 +19,31 @@
|
|||||||
"name": "FooterText",
|
"name": "FooterText",
|
||||||
"caption": "Fusszeile"
|
"caption": "Fusszeile"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
"type": "Label",
|
||||||
|
"caption": "🏦 QR-Einzahlungsschein Daten"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "ValidationTextBox",
|
||||||
|
"name": "CreditorName",
|
||||||
|
"caption": "Rechnungssteller Name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"type": "List",
|
||||||
"name": "Users",
|
"name": "Users",
|
||||||
"caption": "Benutzerliste",
|
"caption": "Benutzerliste",
|
||||||
@@ -34,11 +56,8 @@
|
|||||||
{ "caption": "Adresse", "name": "address", "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" } }
|
{ "caption": "Ort", "name": "city", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||||
]
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"name": "PowerMeters",
|
"name": "PowerMeters",
|
||||||
"caption": "Stromzählerliste",
|
"caption": "Stromzählerliste",
|
||||||
@@ -53,9 +72,7 @@
|
|||||||
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
{ "caption": "Benutzer-ID", "name": "user_id", "width": "20%", "add": "", "edit": { "type": "ValidationTextBox" } }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"name": "WaterMeters",
|
"name": "WaterMeters",
|
||||||
"caption": "Verbrauchszählerliste",
|
"caption": "Verbrauchszählerliste",
|
||||||
@@ -81,12 +98,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"name": "Tariffs",
|
"name": "Tariffs",
|
||||||
"caption": "Tarifübersicht",
|
"caption": "Tarifübersicht",
|
||||||
@@ -94,27 +108,9 @@
|
|||||||
"delete": true,
|
"delete": true,
|
||||||
"sortable": true,
|
"sortable": true,
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{ "caption": "Startdatum", "name": "start", "width": "20%", "add": "", "edit": { "type": "SelectDate" } },
|
||||||
"caption": "Startdatum",
|
{ "caption": "Enddatum", "name": "end", "width": "20%", "add": "", "edit": { "type": "SelectDate" } },
|
||||||
"name": "start",
|
{ "caption": "Tarif (Rp/Einheit)", "name": "price", "width": "20%", "add": 0, "edit": { "type": "NumberSpinner", "digits": 3, "minimum": 0 } },
|
||||||
"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",
|
"caption": "Einheit",
|
||||||
"name": "unit_type",
|
"name": "unit_type",
|
||||||
@@ -134,6 +130,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+150
-57
@@ -44,6 +44,12 @@ class Abrechnung extends IPSModule
|
|||||||
$this->RegisterPropertyString('PropertyText', 'Liegenschaft');
|
$this->RegisterPropertyString('PropertyText', 'Liegenschaft');
|
||||||
$this->RegisterPropertyString('FooterText', 'Belevo AG • 6122 Menznau • www.belevo.ch');
|
$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('FromDate', 'Startdatum', '~UnixTimestamp', 1);
|
||||||
$this->RegisterVariableInteger('ToDate', 'Enddatum', '~UnixTimestamp', 2);
|
$this->RegisterVariableInteger('ToDate', 'Enddatum', '~UnixTimestamp', 2);
|
||||||
$this->RegisterVariableString('LastResult', 'Letzte Abrechnung', '', 3);
|
$this->RegisterVariableString('LastResult', 'Letzte Abrechnung', '', 3);
|
||||||
@@ -139,7 +145,11 @@ class Abrechnung extends IPSModule
|
|||||||
|
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$pdf->AddPage();
|
$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');
|
return $pdf->Output('Abrechnung.pdf', 'S');
|
||||||
@@ -223,6 +233,136 @@ class Abrechnung extends IPSModule
|
|||||||
</h2>
|
</h2>
|
||||||
";
|
";
|
||||||
$pdf->writeHTML($html, true, false, true, false, '');
|
$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) ======================
|
// ====================== Stromkosten (15-Minuten, alle User) ======================
|
||||||
@@ -396,8 +536,8 @@ class Abrechnung extends IPSModule
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
private function GetCalculatedPowerCosts($userId)
|
private function GetCalculatedPowerCosts($userId)
|
||||||
{
|
{
|
||||||
$html = "
|
$html = "
|
||||||
<style>
|
<style>
|
||||||
table.powercost {
|
table.powercost {
|
||||||
@@ -509,7 +649,7 @@ private function GetCalculatedPowerCosts($userId)
|
|||||||
</table>";
|
</table>";
|
||||||
|
|
||||||
return ['html' => $html, 'sum' => $sum];
|
return ['html' => $html, 'sum' => $sum];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====================== Nebenkosten Wasser/Wärme ======================
|
// ====================== Nebenkosten Wasser/Wärme ======================
|
||||||
|
|
||||||
@@ -638,8 +778,8 @@ private function GetCalculatedPowerCosts($userId)
|
|||||||
|
|
||||||
// ====================== Hilfsfunktionen ======================
|
// ====================== Hilfsfunktionen ======================
|
||||||
|
|
||||||
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
|
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
|
||||||
{
|
{
|
||||||
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
|
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
|
||||||
if (!$archiveID || !IPS_VariableExists($varId)) {
|
if (!$archiveID || !IPS_VariableExists($varId)) {
|
||||||
return null;
|
return null;
|
||||||
@@ -671,11 +811,10 @@ private function GetValueAt($varId, $timestamp, $nearestAfter = true)
|
|||||||
|
|
||||||
// Fallback → Live-Wert
|
// Fallback → Live-Wert
|
||||||
return (float)GetValue($varId);
|
return (float)GetValue($varId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
||||||
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
{
|
||||||
{
|
|
||||||
// Werte holen
|
// Werte holen
|
||||||
$startValue = $this->GetValueAt($varId, $tStart, false);
|
$startValue = $this->GetValueAt($varId, $tStart, false);
|
||||||
$endValue = $this->GetValueAt($varId, $tEnd, false);
|
$endValue = $this->GetValueAt($varId, $tEnd, false);
|
||||||
@@ -692,7 +831,7 @@ private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (float)$diff;
|
return (float)$diff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function toUnixTs($val, $endOfDay = false)
|
private function toUnixTs($val, $endOfDay = false)
|
||||||
{
|
{
|
||||||
@@ -724,52 +863,6 @@ private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
|
|||||||
return null;
|
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)
|
private function getTariffPriceAt($tariffs, $typeSynonyms, $ts)
|
||||||
{
|
{
|
||||||
$wanted = array_map('strtolower', $typeSynonyms);
|
$wanted = array_map('strtolower', $typeSynonyms);
|
||||||
|
|||||||
Reference in New Issue
Block a user