Datenschutz vollmacht fixed, two time counter added
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import type { MeterReading } from '../types';
|
||||
import type { MeterReading, ContractMeter } from '../types';
|
||||
|
||||
// Konstante für Gas → kWh Umrechnung (Standard Erdgas H)
|
||||
export const GAS_TO_KWH_FACTOR = 10.5;
|
||||
@@ -8,6 +8,8 @@ export interface ConsumptionCalculation {
|
||||
type: 'exact' | 'projected' | 'insufficient' | 'none';
|
||||
consumptionM3?: number; // Nur bei Gas
|
||||
consumptionKwh: number;
|
||||
consumptionHt?: number; // Nur bei HT/NT: HT-Verbrauch in kWh
|
||||
consumptionNt?: number; // Nur bei HT/NT: NT-Verbrauch in kWh
|
||||
startReading?: MeterReading;
|
||||
endReading?: MeterReading;
|
||||
projectedEndDate?: string; // Nur bei Hochrechnung
|
||||
@@ -17,7 +19,8 @@ export interface ConsumptionCalculation {
|
||||
// Ergebnis der Kostenberechnung
|
||||
export interface CostCalculation {
|
||||
annualBaseCost: number; // basePrice × 12
|
||||
annualConsumptionCost: number; // verbrauch × unitPrice
|
||||
annualConsumptionCost: number; // verbrauch × unitPrice (HT bei Zweitarif)
|
||||
annualConsumptionCostNt?: number; // NT-Verbrauch × unitPriceNt
|
||||
annualTotalCost: number; // Summe
|
||||
monthlyPayment: number; // annualTotalCost / 12
|
||||
bonus?: number;
|
||||
@@ -94,7 +97,6 @@ export function calculateConsumption(
|
||||
(a, b) => new Date(a.readingDate).getTime() - new Date(b.readingDate).getTime()
|
||||
);
|
||||
|
||||
const firstReading = sorted[0];
|
||||
const lastReading = sorted[sorted.length - 1];
|
||||
|
||||
// Prüfen ob Endzählerstand am/nach Vertragsende liegt
|
||||
@@ -103,16 +105,39 @@ export function calculateConsumption(
|
||||
lastReadingDate.setHours(0, 0, 0, 0);
|
||||
contractEndDate.setHours(0, 0, 0, 0);
|
||||
|
||||
// Fall A: Exakter Verbrauch (Endzählerstand am oder nach Vertragsende)
|
||||
// Verbrauch zwischen aufeinanderfolgenden Ständen berechnen
|
||||
// (Erkennt Zählerwechsel: wenn ein Wert sinkt, wird ab dem neuen Stand weitergerechnet)
|
||||
let totalConsumption = 0;
|
||||
let totalConsumptionNt = 0;
|
||||
const hasNt = sorted.some(r => r.valueNt !== undefined && r.valueNt !== null);
|
||||
let effectiveFirstReading = sorted[0];
|
||||
for (let i = 1; i < sorted.length; i++) {
|
||||
const diff = sorted[i].value - sorted[i - 1].value;
|
||||
if (diff < 0) {
|
||||
totalConsumption = 0;
|
||||
totalConsumptionNt = 0;
|
||||
effectiveFirstReading = sorted[i];
|
||||
} else {
|
||||
totalConsumption += diff;
|
||||
if (hasNt && sorted[i].valueNt != null && sorted[i - 1].valueNt != null) {
|
||||
const diffNt = sorted[i].valueNt! - sorted[i - 1].valueNt!;
|
||||
if (diffNt >= 0) totalConsumptionNt += diffNt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const effectiveLastReading = sorted[sorted.length - 1];
|
||||
|
||||
// Fall A: Exakter Verbrauch
|
||||
if (lastReadingDate >= contractEndDate) {
|
||||
const consumption = lastReading.value - firstReading.value;
|
||||
return formatConsumptionResult('exact', consumption, contractType, firstReading, lastReading);
|
||||
const result = formatConsumptionResult('exact', totalConsumption, contractType, effectiveFirstReading, effectiveLastReading);
|
||||
if (hasNt) { result.consumptionHt = totalConsumption; result.consumptionNt = totalConsumptionNt; }
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall B: Hochrechnung erforderlich
|
||||
const daysBetweenReadings = daysDiff(firstReading.readingDate, lastReading.readingDate);
|
||||
const daysBetweenReadings = daysDiff(effectiveFirstReading.readingDate, effectiveLastReading.readingDate);
|
||||
|
||||
// Mindestens 1 Tag zwischen den Messungen für sinnvolle Hochrechnung
|
||||
if (daysBetweenReadings < 1) {
|
||||
return {
|
||||
type: 'insufficient',
|
||||
@@ -122,17 +147,26 @@ export function calculateConsumption(
|
||||
}
|
||||
|
||||
const totalContractDays = daysDiff(startDate, endDate);
|
||||
const measuredConsumption = lastReading.value - firstReading.value;
|
||||
const projectedConsumption = (measuredConsumption / daysBetweenReadings) * totalContractDays;
|
||||
const projectedConsumption = (totalConsumption / daysBetweenReadings) * totalContractDays;
|
||||
|
||||
return formatConsumptionResult(
|
||||
const result = formatConsumptionResult(
|
||||
'projected',
|
||||
projectedConsumption,
|
||||
contractType,
|
||||
firstReading,
|
||||
lastReading,
|
||||
effectiveFirstReading,
|
||||
effectiveLastReading,
|
||||
endDate
|
||||
);
|
||||
|
||||
if (hasNt) {
|
||||
const projectedNt = (totalConsumptionNt / daysBetweenReadings) * totalContractDays;
|
||||
result.consumptionHt = projectedConsumption;
|
||||
result.consumptionNt = projectedNt;
|
||||
// Gesamt-kWh = HT + NT
|
||||
result.consumptionKwh = projectedConsumption + projectedNt;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,7 +207,9 @@ export function calculateCosts(
|
||||
consumptionKwh: number,
|
||||
basePrice?: number,
|
||||
unitPrice?: number,
|
||||
bonus?: number
|
||||
bonus?: number,
|
||||
consumptionNtKwh?: number,
|
||||
unitPriceNt?: number
|
||||
): CostCalculation | null {
|
||||
// Mindestens ein Preis muss vorhanden sein
|
||||
if (basePrice == null && unitPrice == null) {
|
||||
@@ -181,17 +217,78 @@ export function calculateCosts(
|
||||
}
|
||||
|
||||
const annualBaseCost = (basePrice ?? 0) * 12;
|
||||
// Bei HT/NT: consumptionKwh ist nur HT, NT wird separat berechnet
|
||||
const annualConsumptionCost = consumptionKwh * (unitPrice ?? 0);
|
||||
const annualTotalCost = annualBaseCost + annualConsumptionCost;
|
||||
const annualConsumptionCostNt = (consumptionNtKwh ?? 0) * (unitPriceNt ?? 0);
|
||||
const annualTotalCost = annualBaseCost + annualConsumptionCost + annualConsumptionCostNt;
|
||||
const effectiveAnnualCost = annualTotalCost - (bonus ?? 0);
|
||||
const monthlyPayment = effectiveAnnualCost / 12;
|
||||
|
||||
return {
|
||||
annualBaseCost,
|
||||
annualConsumptionCost,
|
||||
annualConsumptionCostNt: annualConsumptionCostNt > 0 ? annualConsumptionCostNt : undefined,
|
||||
annualTotalCost,
|
||||
monthlyPayment,
|
||||
bonus: bonus ?? undefined,
|
||||
effectiveAnnualCost,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet den Verbrauch über mehrere Zähler (Folgezähler).
|
||||
* Pro Zähler wird der Verbrauch einzeln berechnet und dann summiert.
|
||||
*/
|
||||
export function calculateMultiMeterConsumption(
|
||||
contractMeters: ContractMeter[],
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
contractType: 'ELECTRICITY' | 'GAS'
|
||||
): ConsumptionCalculation {
|
||||
if (contractMeters.length === 0) {
|
||||
return { type: 'none', consumptionKwh: 0 };
|
||||
}
|
||||
|
||||
let totalConsumption = 0;
|
||||
let totalConsumptionM3 = 0;
|
||||
let hasExact = true;
|
||||
let hasAny = false;
|
||||
let firstStart: MeterReading | undefined;
|
||||
let lastEnd: MeterReading | undefined;
|
||||
|
||||
for (const cm of contractMeters) {
|
||||
const readings = cm.meter?.readings || [];
|
||||
if (readings.length === 0) continue;
|
||||
|
||||
// Zeitraum für diesen Zähler bestimmen
|
||||
const meterStart = cm.installedAt || startDate;
|
||||
const meterEnd = cm.removedAt || endDate;
|
||||
|
||||
const result = calculateConsumption(readings, meterStart, meterEnd, contractType);
|
||||
|
||||
if (result.type === 'none' || result.type === 'insufficient') continue;
|
||||
|
||||
hasAny = true;
|
||||
if (result.type === 'projected') hasExact = false;
|
||||
|
||||
totalConsumption += result.consumptionKwh;
|
||||
if (result.consumptionM3) totalConsumptionM3 += result.consumptionM3;
|
||||
|
||||
if (!firstStart && result.startReading) firstStart = result.startReading;
|
||||
if (result.endReading) lastEnd = result.endReading;
|
||||
}
|
||||
|
||||
if (!hasAny) {
|
||||
// Fallback: Einzelzähler-Berechnung mit allen Readings
|
||||
const allReadings = contractMeters.flatMap(cm => cm.meter?.readings || []);
|
||||
return calculateConsumption(allReadings, startDate, endDate, contractType);
|
||||
}
|
||||
|
||||
return {
|
||||
type: hasExact ? 'exact' : 'projected',
|
||||
consumptionKwh: totalConsumption,
|
||||
consumptionM3: contractType === 'GAS' ? totalConsumptionM3 : undefined,
|
||||
startReading: firstStart,
|
||||
endReading: lastEnd,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Navigation-History über location.state.
|
||||
* Jeder Link fügt die aktuelle URL zum Stack hinzu.
|
||||
* Der Zurück-Button poppt den letzten Eintrag und gibt den Rest weiter.
|
||||
*/
|
||||
|
||||
export interface NavState {
|
||||
history?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt den state für einen Link - fügt die aktuelle URL zum History-Stack hinzu.
|
||||
*/
|
||||
export function pushHistory(currentPath: string, locationState?: unknown): NavState {
|
||||
const prev = (locationState as NavState)?.history || [];
|
||||
return { history: [...prev, currentPath] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt die Zurück-URL und den verbleibenden History-Stack zurück.
|
||||
*/
|
||||
export function popHistory(locationState?: unknown, fallback?: string): { to: string; state: NavState } {
|
||||
const history = [...((locationState as NavState)?.history || [])];
|
||||
const to = history.pop() || fallback || '/';
|
||||
return { to, state: { history } };
|
||||
}
|
||||
Reference in New Issue
Block a user