Endstand alter Zähler fließt in Verbrauchsberechnung ein
Bisher wurde "Letzter Stand alter Zähler" zwar in ContractMeter.finalReading gespeichert, aber nirgends ausgewertet. Neuer Helper recordPredecessorFinalReading legt am Wechseldatum einen regulären MeterReading-Eintrag für den Vorgänger an (idempotent, mit Validierung gegen vorhandene Stände). Aufgerufen aus addSuccessorMeter (Vertragsansicht) und createMeter mit successorOf (Kundenakte). Folge: Der Endstand erscheint in der Zählerstände-Liste des alten Zählers und fließt automatisch über calculateMultiMeterConsumption in den Verbrauch (Zeitraum bis removedAt ist inklusive). UI-Hinweise in beiden Folgezähler-Forms erklären den Effekt. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -421,6 +421,45 @@ export async function getCustomerMeters(
|
||||
});
|
||||
}
|
||||
|
||||
// Schreibt den Endstand des Vorgänger-Zählers beim Zählerwechsel als
|
||||
// MeterReading. Wird beim Folgezähler-Anlegen aufgerufen (sowohl aus der
|
||||
// Kundenakte als auch aus der Vertragsansicht). Idempotent: existiert am
|
||||
// Wechseltag schon ein Reading, wird nichts angelegt. Validierung
|
||||
// monoton-steigend wird durchgereicht – wirft bei Konflikt.
|
||||
export async function recordPredecessorFinalReading(
|
||||
predecessorMeterId: number,
|
||||
switchAt: Date,
|
||||
value: number,
|
||||
) {
|
||||
const meter = await prisma.meter.findUnique({
|
||||
where: { id: predecessorMeterId },
|
||||
select: { type: true },
|
||||
});
|
||||
if (!meter) return;
|
||||
|
||||
const dayStart = new Date(switchAt);
|
||||
dayStart.setHours(0, 0, 0, 0);
|
||||
const dayEnd = new Date(dayStart);
|
||||
dayEnd.setDate(dayEnd.getDate() + 1);
|
||||
|
||||
const existingSameDay = await prisma.meterReading.findFirst({
|
||||
where: { meterId: predecessorMeterId, readingDate: { gte: dayStart, lt: dayEnd } },
|
||||
});
|
||||
if (existingSameDay) return;
|
||||
|
||||
await validateReadingValue(predecessorMeterId, switchAt, value, undefined, 'HT');
|
||||
|
||||
await prisma.meterReading.create({
|
||||
data: {
|
||||
meterId: predecessorMeterId,
|
||||
readingDate: switchAt,
|
||||
value,
|
||||
unit: meter.type === 'GAS' ? 'm³' : 'kWh',
|
||||
notes: 'Endstand bei Zählerwechsel (automatisch beim Folgezähler-Anlegen erfasst)',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Lieferadresse muss zum Kunden gehören und vom Typ DELIVERY_RESIDENCE sein.
|
||||
// Wirft eine sprechende Fehlermeldung, die der Controller dem User durchreicht.
|
||||
async function assertDeliveryAddressBelongsToCustomer(addressId: number, customerId: number) {
|
||||
@@ -471,6 +510,21 @@ export async function createMeter(
|
||||
throw new Error('Vorgänger-Zähler muss denselben Typ haben (Strom/Gas)');
|
||||
}
|
||||
predecessor = pred;
|
||||
|
||||
// Endstand bereits hier validieren, damit kein verwaister Meter entsteht
|
||||
// wenn der Wert mit bestehenden Zählerständen kollidiert.
|
||||
if (data.successorOf.finalReadingPrevious != null) {
|
||||
const switchAt = data.successorOf.installedAt
|
||||
? new Date(data.successorOf.installedAt)
|
||||
: new Date();
|
||||
await validateReadingValue(
|
||||
pred.id,
|
||||
switchAt,
|
||||
data.successorOf.finalReadingPrevious,
|
||||
undefined,
|
||||
'HT',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const created = await prisma.meter.create({
|
||||
@@ -557,6 +611,17 @@ export async function createMeter(
|
||||
data: { meterId: created.id },
|
||||
});
|
||||
}
|
||||
|
||||
// Endstand des Vorgängers als regulären Zählerstand erfassen, damit er
|
||||
// in die Verbrauchsberechnung einfließt und in der Zählerstände-Liste
|
||||
// sichtbar ist. Idempotent gegen Doppel-Submit.
|
||||
if (data.successorOf.finalReadingPrevious != null) {
|
||||
await recordPredecessorFinalReading(
|
||||
predecessor.id,
|
||||
installedAt,
|
||||
data.successorOf.finalReadingPrevious,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return created;
|
||||
|
||||
Reference in New Issue
Block a user