no message
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user