no message

This commit is contained in:
2025-12-05 10:24:15 +01:00
parent 17aed355b3
commit 82e4d4c523

View File

@@ -20,7 +20,11 @@ class Abrechnung extends IPSModule
$this->EnableAction('FromDate');
$this->EnableAction('ToDate');
$this->RegisterScript('StartBilling', 'Abrechnung starten', "<?php IPS_RequestAction(" . $this->InstanceID . ", 'StartBilling', ''); ?>");
$this->RegisterScript(
'StartBilling',
'Abrechnung starten',
"<?php IPS_RequestAction(" . $this->InstanceID . ", 'StartBilling', ''); ?>"
);
$this->RegisterMediaDocument('InvoicePDF', 'Letzte Rechnung', 'pdf');
}
@@ -85,6 +89,7 @@ class Abrechnung extends IPSModule
return false;
}
// Stromkosten einmal für alle User berechnen
$this->CalculateAllPowerCosts($power, $tariffs, $from, $to);
$pdf = new TCPDF('P', 'mm', 'A4', true, 'UTF-8', false);
@@ -109,10 +114,12 @@ class Abrechnung extends IPSModule
<p>{$user['address']}<br>{$user['city']}</p>
<p><strong>Zeitraum:</strong> " . date('d.m.Y', $from) . " " . date('d.m.Y', $to) . "</p><br>";
// Stromkosten (aus globaler Berechnung)
$powerResult = $this->GetCalculatedPowerCosts($user['id']);
$html .= "<h3>⚡ Stromkosten</h3>" . $powerResult['html'];
$totalPower = $powerResult['sum'];
// Nebenkosten (Wasser/Wärme)
$additionalResult = $this->CalculateAdditionalCosts($water, $tariffs, $user['id'], $from, $to);
$html .= "<h3>💧 Nebenkosten (Wasser/Wärme)</h3>" . $additionalResult['html'];
$totalAdditional = $additionalResult['sum'];
@@ -123,13 +130,18 @@ class Abrechnung extends IPSModule
$pdf->writeHTML($html, true, false, true, false, '');
}
// ====================== Stromkosten (15-Minuten, alle User) ======================
private function CalculateAllPowerCosts($powerMeters, $tariffs, $from, $to)
{
$this->powerCostCache = [];
// Zähler je User aufbauen
$metersByUser = [];
foreach ($powerMeters as $m) {
if (!isset($m['user_id'])) continue;
if (!isset($m['user_id'])) {
continue;
}
$userId = (int)$m['user_id'];
$name = $m['name'] ?? ('Meter_' . $m['user_id']);
@@ -159,11 +171,15 @@ class Abrechnung extends IPSModule
];
}
if (empty($metersByUser)) return;
if (empty($metersByUser)) {
return;
}
// 15-Minuten-Schritte
for ($ts = $from; $ts < $to; $ts += 900) {
$slotEnd = min($to, $ts + 900);
// Tarife (gleich für alle User)
$pGrid = $this->getTariffPriceAt($tariffs, ['Netztarif'], $ts);
$pSolar = $this->getTariffPriceAt($tariffs, ['Solartarif'], $ts);
$pFeed = $this->getTariffPriceAt($tariffs, ['Einspeisetarif'], $ts);
@@ -174,6 +190,7 @@ class Abrechnung extends IPSModule
$expTotal = 0.0;
$slot = [];
// Deltas je Zähler des Users
foreach ($meters as $name => $mm) {
$impDelta = 0.0;
@@ -193,19 +210,23 @@ class Abrechnung extends IPSModule
}
}
if ($impTotal == 0.0 && $expTotal == 0.0) continue;
if ($impTotal <= $expTotal && $expTotal > 0.0) {
$ratio = $impTotal / $expTotal;
$pvCoversAll = true;
} elseif ($impTotal > 0.0) {
$ratio = $expTotal / $impTotal;
$pvCoversAll = false;
} else {
$ratio = 0.0;
$pvCoversAll = true;
if ($impTotal == 0.0 && $expTotal == 0.0) {
continue;
}
// Verhältnis PV / Netz pro User
if ($impTotal <= $expTotal && $expTotal > 0.0) {
$ratio = $impTotal / $expTotal;
$pvCoversAll = true;
} elseif ($impTotal > 0.0) {
$ratio = $expTotal / $impTotal;
$pvCoversAll = false;
} else {
$ratio = 0.0;
$pvCoversAll = true;
}
// Werte pro Zähler verteilen
foreach ($slot as $name => $v) {
$imp = $v['imp'];
$exp = $v['exp'];
@@ -214,6 +235,7 @@ class Abrechnung extends IPSModule
$this->powerCostCache[$userId][$name]['exp'] += $exp;
if ($pvCoversAll) {
// PV deckt gesamten Verbrauch
$this->powerCostCache[$userId][$name]['solar_bezug'] += $imp;
$this->powerCostCache[$userId][$name]['solareinspeisung'] += (1 - $ratio) * $exp;
$this->powerCostCache[$userId][$name]['solarverkauf'] += $ratio * $exp;
@@ -225,6 +247,7 @@ class Abrechnung extends IPSModule
$this->powerCostCache[$userId][$name]['rev_feedin'] += ((1 - $ratio) * $exp * $pFeed) / 100.0;
}
} else {
// Teil Netzbezug
$this->powerCostCache[$userId][$name]['solar_bezug'] += $ratio * $imp;
$this->powerCostCache[$userId][$name]['netz_bezug'] += (1 - $ratio) * $imp;
$this->powerCostCache[$userId][$name]['solarverkauf'] += $exp;
@@ -295,6 +318,8 @@ class Abrechnung extends IPSModule
return ['html' => $html, 'sum' => $sum];
}
// ====================== Nebenkosten Wasser/Wärme ======================
private function CalculateAdditionalCosts($waterMeters, $tariffs, $userId, $from, $to)
{
$html = "<table border='1' cellspacing='0' cellpadding='3' width='100%' style='font-size:6px;'>
@@ -303,11 +328,13 @@ class Abrechnung extends IPSModule
<th>Zähler Start</th><th>Zähler Ende</th><th>Verbrauch</th><th>Tarif (Rp)</th><th>Kosten (CHF)</th>
</tr>";
$total = 0.0;
$total = 0.0;
$usedTariffs = [];
foreach ($waterMeters as $m) {
if ($m['user_id'] != $userId) continue;
if ($m['user_id'] != $userId) {
continue;
}
$type = $m['meter_type'] ?? 'Warmwasser';
$cost = $this->AddMeterToPDFRow($m, $tariffs, $from, $to, $type);
$html .= $cost['row'];
@@ -334,12 +361,14 @@ class Abrechnung extends IPSModule
private function AddMeterToPDFRow($meter, $tariffs, $from, $to, $type)
{
$rows = '';
$totalCost = 0.0;
$rows = '';
$totalCost = 0.0;
$usedTariffs = [];
$varId = $meter['var_consumption'];
$varId = $meter['var_consumption'];
if (!IPS_VariableExists($varId)) return ['row' => '', 'value' => 0, 'tariffs' => []];
if (!IPS_VariableExists($varId)) {
return ['row' => '', 'value' => 0, 'tariffs' => []];
}
$filteredTariffs = array_filter($tariffs, fn($t) =>
strtolower(trim($t['unit_type'] ?? '')) === strtolower(trim($type))
@@ -366,8 +395,8 @@ class Abrechnung extends IPSModule
$activeTariff = ['start_ts' => $currentStart, 'end_ts' => $to, 'price' => 0.0];
}
$tariffEnd = intval($activeTariff['end_ts']);
$tariffPrice = floatval($activeTariff['price']);
$tariffEnd = (int)$activeTariff['end_ts'];
$tariffPrice = (float)$activeTariff['price'];
$key = $activeTariff['start_ts'] . '-' . $activeTariff['end_ts'] . '-' . $tariffPrice;
if (!isset($usedTariffs[$key])) {
@@ -382,10 +411,12 @@ class Abrechnung extends IPSModule
$segmentEnd = ($tariffEnd < $to) ? $tariffEnd : $to;
$endValue = $this->GetValueAt($varId, $segmentEnd, true);
if ($startValue === null || $endValue === null) break;
if ($startValue === null || $endValue === null) {
break;
}
$verbrauch = max(0, $endValue - $startValue);
$kosten = round(($tariffPrice / 100) * $verbrauch, 2);
$kosten = round(($tariffPrice / 100) * $verbrauch, 2);
$totalCost += $kosten;
$rows .= "<tr>
@@ -400,40 +431,55 @@ class Abrechnung extends IPSModule
<td align='right'>" . number_format($kosten, 2) . "</td>
</tr>";
if ($tariffEnd < $to) $currentStart = $tariffEnd + 1;
else break;
if ($tariffEnd < $to) {
$currentStart = $tariffEnd + 1;
} else {
break;
}
}
if ($rows === '') return ['row' => '', 'value' => 0, 'tariffs' => []];
if ($rows === '') {
return ['row' => '', 'value' => 0, 'tariffs' => []];
}
return ['row' => $rows, 'value' => $totalCost, 'tariffs' => array_values($usedTariffs)];
}
// ====================== Hilfsfunktionen ======================
private function GetValueAt($varId, $timestamp, $nearestAfter = true)
{
$archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
if (!$archiveID || !IPS_VariableExists($varId)) return null;
if (!$archiveID || !IPS_VariableExists($varId)) {
return null;
}
// 30 Tage Fenster um den Zeitpunkt
$values = @AC_GetLoggedValues($archiveID, $varId, $timestamp - 30 * 86400, $timestamp + 30 * 86400, 0);
if (empty($values)) return floatval(GetValue($varId));
if (empty($values)) {
return (float)GetValue($varId);
}
$closest = null;
foreach ($values as $v) {
if ($nearestAfter && $v['TimeStamp'] >= $timestamp) {
// erster Wert NACH oder GENAU zum Timestamp
$closest = $v['Value'];
break;
} elseif (!$nearestAfter && $v['TimeStamp'] <= $timestamp) {
// letzter Wert DAVOR oder GENAU zum Timestamp
$closest = $v['Value'];
}
}
return $closest ?? floatval(GetValue($varId));
return $closest ?? (float)GetValue($varId);
}
private function getDeltaFromArchive(int $varId, int $tStart, int $tEnd): float
{
// Beide Werte: immer letzter geloggter Wert VOR/GENAU zum Zeitpunkt
$startValue = $this->GetValueAt($varId, $tStart, false);
$endValue = $this->GetValueAt($varId, $tEnd, true);
$endValue = $this->GetValueAt($varId, $tEnd, false);
if ($startValue === null || $endValue === null) {
return 0.0;
@@ -441,6 +487,7 @@ class Abrechnung extends IPSModule
$diff = $endValue - $startValue;
if ($diff < 0) {
// Sicherheitsnetz bei Zähler-Reset
$diff = 0.0;
}
return (float)$diff;
@@ -448,8 +495,12 @@ class Abrechnung extends IPSModule
private function toUnixTs($val, $endOfDay = false)
{
if (is_int($val)) return $val;
if (is_numeric($val)) return intval($val);
if (is_int($val)) {
return $val;
}
if (is_numeric($val)) {
return (int)$val;
}
if (is_string($val)) {
$s = trim($val);
@@ -457,7 +508,13 @@ class Abrechnung extends IPSModule
$obj = json_decode($s, true);
if (isset($obj['year'], $obj['month'], $obj['day'])) {
$time = $endOfDay ? '23:59:59' : '00:00:00';
return strtotime(sprintf('%04d-%02d-%02d %s', $obj['year'], $obj['month'], $obj['day'], $time));
return strtotime(sprintf(
'%04d-%02d-%02d %s',
$obj['year'],
$obj['month'],
$obj['day'],
$time
));
}
}
$ts = strtotime($s);
@@ -468,27 +525,45 @@ class Abrechnung extends IPSModule
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) => intval($a['TimeStamp']) <=> intval($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 (!is_int($tStart)) {
$tStart = strtotime($tStart);
}
if (!is_int($tEnd)) {
$tEnd = strtotime($tEnd);
}
if ($vStart === null) $vStart = floatval(GetValue($varId));
if ($vEnd === null) $vEnd = floatval(GetValue($varId));
$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;
@@ -497,17 +572,27 @@ class Abrechnung extends IPSModule
private function getTariffPriceAt($tariffs, $typeSynonyms, $ts)
{
$wanted = array_map('strtolower', $typeSynonyms);
$cands = [];
$cands = [];
foreach ($tariffs as $t) {
$u = strtolower(trim($t['unit_type'] ?? ''));
if (!in_array($u, $wanted)) continue;
if (!in_array($u, $wanted, true)) {
continue;
}
$s = $this->toUnixTs($t['start'], false);
$e = $this->toUnixTs($t['end'], true);
if (!$s || !$e) continue;
if ($s <= $ts && $ts <= $e) $cands[] = floatval($t['price']);
if (!$s || !$e) {
continue;
}
if ($s <= $ts && $ts <= $e) {
$cands[] = (float)$t['price'];
}
}
if (empty($cands)) {
return null;
}
if (empty($cands)) return null;
return end($cands);
}
}