diff --git a/Abrechnung/module.php b/Abrechnung/module.php index 68d3e39..2a14cbe 100644 --- a/Abrechnung/module.php +++ b/Abrechnung/module.php @@ -225,16 +225,18 @@ private function GetValueAt(int $varId, int $timestamp, bool $nearestAfter = tru return null; } - $maxDays = 365; // 🔹 maximal 1 Jahr zurück / vor suchen - $stepDays = 7; // 🔹 Schrittweise Erweiterung (jeweils 7 Tage) + $maxDays = 365; // maximal 1 Jahr suchen + $stepDays = 30; // in 30-Tage-Blöcken durchsuchen $valueFound = null; if ($nearestAfter) { - for ($offset = 0; $offset <= $maxDays; $offset += $stepDays) { + // 🔹 Suche den ersten Wert NACH dem gewünschten Zeitpunkt + for ($offset = 0; $offset < $maxDays; $offset += $stepDays) { $from = $timestamp + $offset * 86400; $to = $from + $stepDays * 86400; - $values = @AC_GetLoggedValues($archiveID, $varId, $from, $to, 1000); + $values = @AC_GetLoggedValues($archiveID, $varId, $from, $to, 0); if (!empty($values)) { + // ersten Wert nach Timestamp nehmen foreach ($values as $v) { if ($v['TimeStamp'] >= $timestamp) { $valueFound = floatval($v['Value']); @@ -244,93 +246,150 @@ private function GetValueAt(int $varId, int $timestamp, bool $nearestAfter = tru } } } else { - for ($offset = 0; $offset <= $maxDays; $offset += $stepDays) { + // 🔹 Suche den letzten Wert VOR dem gewünschten Zeitpunkt + for ($offset = 0; $offset < $maxDays; $offset += $stepDays) { $from = $timestamp - ($offset + $stepDays) * 86400; $to = $timestamp - $offset * 86400; - $values = @AC_GetLoggedValues($archiveID, $varId, $from, $to, 1000); + $values = @AC_GetLoggedValues($archiveID, $varId, $from, $to, 0); if (!empty($values)) { - $valueFound = floatval($values[count($values) - 1]['Value']); - break; + // letzten Wert vor Timestamp nehmen + $last = null; + foreach ($values as $v) { + if ($v['TimeStamp'] <= $timestamp) { + $last = $v; + } else { + break; + } + } + if ($last !== null) { + $valueFound = floatval($last['Value']); + break; + } } } } - if ($valueFound !== null) { - return $valueFound; + // 🔹 Falls nichts im Archiv: aktuellen Variablenwert nehmen + if ($valueFound === null) { + $fallback = floatval(GetValue($varId)); + IPS_LogMessage('Abrechnung', "⚠ Kein Archivwert für $varId gefunden – nutze aktuellen Wert ($fallback)"); + return $fallback; } - // ❗ Fallback, wenn gar nichts im Archiv gefunden wird - $fallback = floatval(GetValue($varId)); - IPS_LogMessage('Abrechnung', "⚠ Kein Archivwert für $varId im Umkreis von $maxDays Tagen gefunden, nutze aktuellen Wert ($fallback)"); - return $fallback; + return $valueFound; } private function AddMeterToPDFRow($meter, $tariffs, $from, $to, $type) { - $archiveID = @IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0]; - if (!$archiveID || !IPS_VariableExists($meter['var_consumption'])) { - IPS_LogMessage('Abrechnung', "❌ AddMeterToPDFRow: Archiv oder Variable {$meter['var_consumption']} nicht gefunden"); - return ['row' => '', 'value' => 0]; - } - $rows = ''; $totalCost = 0.0; + $varId = $meter['var_consumption']; - // Tarife nach Startzeit sortieren - usort($tariffs, fn($a, $b) => strtotime($a['start']) <=> strtotime($b['start'])); - - // Filter: nur Tarife des Typs (z. B. Strombezug, Warmwasser) - $relevantTariffs = array_filter($tariffs, fn($t) => strtolower($t['unit_type']) == strtolower($type)); - if (empty($relevantTariffs)) { - IPS_LogMessage('Abrechnung', "⚠ Keine Tarife für Typ $type gefunden"); + if (!IPS_VariableExists($varId)) { + IPS_LogMessage('Abrechnung', "❌ Variable {$varId} für {$meter['name']} nicht gefunden"); return ['row' => '', 'value' => 0]; } - // Iteriere über alle Tarifabschnitte, die den Abrechnungszeitraum ganz oder teilweise überlappen - foreach ($relevantTariffs as $t) { - $tStart = strtotime($t['start']); - $tEnd = strtotime($t['end']); + // 🔹 1. Tarife nach Typ filtern + $filteredTariffs = array_filter($tariffs, function ($t) use ($type) { + $typeNorm = strtolower(trim($type)); + $tariffType = strtolower(trim($t['unit_type'] ?? '')); + $map = [ + 'strombezug' => ['strom', 'strombezug', 'stromzähler', 'stromverbrauch'], + 'warmwasser' => ['warmwasser', 'wasser', 'hww'], + 'kaltwasser' => ['kaltwasser', 'wasser', 'kww'], + 'wärme' => ['wärme', 'heizung', 'heat'] + ]; + return in_array($tariffType, $map[$typeNorm] ?? []) || $tariffType === $typeNorm; + }); - // Überlappung mit Abrechnungszeitraum prüfen - if ($tEnd < $from || $tStart > $to) continue; + if (empty($filteredTariffs)) { + IPS_LogMessage('Abrechnung', "⚠ Keine passenden Tarife für $type gefunden"); + return ['row' => '', 'value' => 0]; + } - // Effektive Tarifperiode innerhalb des Abrechnungszeitraums bestimmen - $periodStart = max($from, $tStart); - $periodEnd = min($to, $tEnd); + // 🔹 2. Zeitstempel konvertieren & sortieren + foreach ($filteredTariffs as &$t) { + $t['start_ts'] = is_numeric($t['start']) ? intval($t['start']) : strtotime($t['start']); + $t['end_ts'] = is_numeric($t['end']) ? intval($t['end']) : strtotime($t['end']); + } + unset($t); + usort($filteredTariffs, fn($a, $b) => $a['start_ts'] <=> $b['start_ts']); - // Archivwerte zu Beginn und Ende der Teilperiode holen - $valueStart = $this->GetValueAt($meter['var_consumption'], $periodStart, false); - $valueEnd = $this->GetValueAt($meter['var_consumption'], $periodEnd, true); - - if ($valueStart === null || $valueEnd === null) { - IPS_LogMessage('Abrechnung', "⚠ Keine gültigen Werte für {$meter['name']} zwischen $periodStart und $periodEnd"); - continue; + $current = $from; + while ($current < $to) { + // 🔹 Aktiven Tarif finden + $active = null; + foreach ($filteredTariffs as $t) { + if ($t['start_ts'] <= $current && $current < $t['end_ts']) { + $active = $t; + break; + } } - // Verbrauch & Kosten berechnen - $verbrauch = max(0, $valueEnd - $valueStart); - $tarifRp = floatval($t['price']); - $kostenCHF = round(($tarifRp / 100) * $verbrauch, 2); - $totalCost += $kostenCHF; + // 🔹 Zeitraumgrenzen bestimmen + if ($active) { + $segmentEnd = min($active['end_ts'], $to); + $tariffPrice = floatval($active['price']); + $tariffLabel = number_format($tariffPrice, 2); + } else { + // kein Tarif → bis zum nächsten bekannten oder Abrechnungsende + $nextStart = $to; + foreach ($filteredTariffs as $t) { + if ($t['start_ts'] > $current && $t['start_ts'] < $nextStart) { + $nextStart = $t['start_ts']; + } + } + $segmentEnd = min($nextStart, $to); + $tariffPrice = 0.0; + $tariffLabel = 'kein Tarif'; + } - // Tabellenzeile erzeugen + // Sicherheitsnetz (kein 0-Länge-Segment) + if ($segmentEnd <= $current) break; + + // 🔹 Verbrauch holen & Kosten berechnen + $startVal = $this->GetValueAt($varId, $current, false); + $endVal = $this->GetValueAt($varId, $segmentEnd, true); + $diff = max(0, $endVal - $startVal); + $costCHF = round(($tariffPrice / 100) * $diff, 2); + $totalCost += $costCHF; + + // 🔹 HTML-Zeile schreiben $rows .= "