Datenschutz vollmacht fixed, two time counter added
This commit is contained in:
@@ -146,13 +146,35 @@ export async function updateAuthorizationDocument(
|
||||
* Vollmacht-Dokument löschen
|
||||
*/
|
||||
export async function deleteAuthorizationDocument(customerId: number, representativeId: number) {
|
||||
// Prüfen ob die Vollmacht per Papier erteilt wurde
|
||||
const auth = await prisma.representativeAuthorization.findUnique({
|
||||
where: { customerId_representativeId: { customerId, representativeId } },
|
||||
select: { source: true, documentPath: true },
|
||||
});
|
||||
|
||||
if (!auth) throw new Error('Vollmacht nicht gefunden');
|
||||
|
||||
// Datei löschen
|
||||
if (auth.documentPath) {
|
||||
try {
|
||||
const filePath = path.join(process.cwd(), auth.documentPath);
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Löschen der Vollmacht-PDF:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Wenn per Papier erteilt → Vollmacht widerrufen
|
||||
// Wenn per Portal/Online erteilt → nur PDF entfernen, Vollmacht bleibt
|
||||
const withdrawData = auth.source === 'papier'
|
||||
? { documentPath: null, isGranted: false, withdrawnAt: new Date() }
|
||||
: { documentPath: null };
|
||||
|
||||
return prisma.representativeAuthorization.update({
|
||||
where: {
|
||||
customerId_representativeId: { customerId, representativeId },
|
||||
},
|
||||
data: {
|
||||
documentPath: null,
|
||||
},
|
||||
where: { customerId_representativeId: { customerId, representativeId } },
|
||||
data: withdrawData,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -125,14 +125,14 @@ export async function getContractById(id: number, decryptPassword = false) {
|
||||
previousProvider: true,
|
||||
previousContract: {
|
||||
include: {
|
||||
energyDetails: { include: { meter: { include: { readings: true } }, invoices: true } },
|
||||
energyDetails: { include: { meter: { include: { readings: true } }, contractMeters: { include: { meter: { include: { readings: true } } }, orderBy: { position: 'asc' as const } }, invoices: true } },
|
||||
internetDetails: { include: { phoneNumbers: true } },
|
||||
mobileDetails: { include: { simCards: true } },
|
||||
tvDetails: true,
|
||||
carInsuranceDetails: true,
|
||||
},
|
||||
},
|
||||
energyDetails: { include: { meter: { include: { readings: true } }, invoices: true } },
|
||||
energyDetails: { include: { meter: { include: { readings: true } }, contractMeters: { include: { meter: { include: { readings: true } } }, orderBy: { position: 'asc' as const } }, invoices: true } },
|
||||
internetDetails: { include: { phoneNumbers: true } },
|
||||
mobileDetails: { include: { simCards: true } },
|
||||
tvDetails: true,
|
||||
@@ -403,11 +403,45 @@ export async function updateContract(
|
||||
|
||||
// Update type-specific details
|
||||
if (energyDetails) {
|
||||
const existingEcd = await prisma.energyContractDetails.findUnique({
|
||||
where: { contractId: id },
|
||||
select: { id: true, meterId: true },
|
||||
});
|
||||
|
||||
await prisma.energyContractDetails.upsert({
|
||||
where: { contractId: id },
|
||||
update: energyDetails,
|
||||
create: { contractId: id, ...energyDetails },
|
||||
});
|
||||
|
||||
// ContractMeter synchronisieren wenn sich der Zähler ändert
|
||||
if (energyDetails.meterId !== undefined && existingEcd) {
|
||||
const oldMeterId = existingEcd.meterId;
|
||||
const newMeterId = energyDetails.meterId;
|
||||
|
||||
if (oldMeterId !== newMeterId) {
|
||||
// Alle alten ContractMeter-Einträge entfernen
|
||||
await prisma.contractMeter.deleteMany({
|
||||
where: { energyContractDetailsId: existingEcd.id },
|
||||
});
|
||||
|
||||
// Neuen ContractMeter-Eintrag erstellen (wenn ein Zähler gesetzt)
|
||||
if (newMeterId) {
|
||||
const contract = await prisma.contract.findUnique({
|
||||
where: { id },
|
||||
select: { startDate: true },
|
||||
});
|
||||
await prisma.contractMeter.create({
|
||||
data: {
|
||||
energyContractDetailsId: existingEcd.id,
|
||||
meterId: newMeterId,
|
||||
position: 0,
|
||||
installedAt: contract?.startDate,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (internetDetails) {
|
||||
|
||||
@@ -89,6 +89,11 @@ export interface ReportedMeterReading {
|
||||
customerNumber: string;
|
||||
name: string;
|
||||
};
|
||||
// Zugehöriger Vertrag
|
||||
contract?: {
|
||||
id: number;
|
||||
contractNumber: string;
|
||||
};
|
||||
// Anbieter-Info für Quick-Login
|
||||
providerPortal?: {
|
||||
providerName: string;
|
||||
@@ -810,6 +815,7 @@ async function getReportedMeterReadings(): Promise<ReportedMeterReading[]> {
|
||||
contract: {
|
||||
select: {
|
||||
id: true,
|
||||
contractNumber: true,
|
||||
portalUsername: true,
|
||||
provider: {
|
||||
select: { id: true, name: true, portalUrl: true },
|
||||
@@ -847,6 +853,10 @@ async function getReportedMeterReadings(): Promise<ReportedMeterReading[]> {
|
||||
customerNumber: r.meter.customer.customerNumber,
|
||||
name: `${r.meter.customer.firstName} ${r.meter.customer.lastName}`,
|
||||
},
|
||||
contract: contract ? {
|
||||
id: contract.id,
|
||||
contractNumber: contract.contractNumber,
|
||||
} : undefined,
|
||||
providerPortal: provider?.portalUrl ? {
|
||||
providerName: provider.name,
|
||||
portalUrl: provider.portalUrl,
|
||||
|
||||
@@ -443,6 +443,30 @@ export async function updateMeter(
|
||||
}
|
||||
|
||||
export async function deleteMeter(id: number) {
|
||||
// Prüfen ob der Zähler noch an Verträgen hängt
|
||||
const linkedContracts = await prisma.contractMeter.findMany({
|
||||
where: { meterId: id },
|
||||
include: { energyContractDetails: { include: { contract: { select: { contractNumber: true } } } } },
|
||||
});
|
||||
|
||||
if (linkedContracts.length > 0) {
|
||||
const contractNumbers = linkedContracts
|
||||
.map(cm => cm.energyContractDetails.contract.contractNumber)
|
||||
.join(', ');
|
||||
throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`);
|
||||
}
|
||||
|
||||
// Auch direkte meterId-Referenz auf EnergyContractDetails prüfen
|
||||
const directLinks = await prisma.energyContractDetails.findMany({
|
||||
where: { meterId: id },
|
||||
include: { contract: { select: { contractNumber: true } } },
|
||||
});
|
||||
|
||||
if (directLinks.length > 0) {
|
||||
const contractNumbers = directLinks.map(d => d.contract.contractNumber).join(', ');
|
||||
throw new Error(`Zähler kann nicht gelöscht werden – noch an Vertrag/Verträgen zugeordnet: ${contractNumbers}`);
|
||||
}
|
||||
|
||||
return prisma.meter.delete({ where: { id } });
|
||||
}
|
||||
|
||||
@@ -451,14 +475,25 @@ export async function addMeterReading(
|
||||
data: {
|
||||
readingDate: Date;
|
||||
value: number;
|
||||
valueNt?: number;
|
||||
unit?: string;
|
||||
notes?: string;
|
||||
}
|
||||
) {
|
||||
// Validierung: Zählerstand muss monoton steigend sein
|
||||
await validateReadingValue(meterId, data.readingDate, data.value, undefined, 'HT');
|
||||
if (data.valueNt !== undefined) {
|
||||
await validateReadingValue(meterId, data.readingDate, data.valueNt, undefined, 'NT');
|
||||
}
|
||||
|
||||
return prisma.meterReading.create({
|
||||
data: {
|
||||
meterId,
|
||||
...data,
|
||||
readingDate: data.readingDate,
|
||||
value: data.value,
|
||||
valueNt: data.valueNt,
|
||||
unit: data.unit,
|
||||
notes: data.notes,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -476,6 +511,7 @@ export async function updateMeterReading(
|
||||
data: {
|
||||
readingDate?: Date;
|
||||
value?: number;
|
||||
valueNt?: number | null;
|
||||
unit?: string;
|
||||
notes?: string;
|
||||
}
|
||||
@@ -489,12 +525,65 @@ export async function updateMeterReading(
|
||||
throw new Error('Zählerstand nicht gefunden');
|
||||
}
|
||||
|
||||
// Validierung bei Wertänderung
|
||||
if (data.value !== undefined || data.readingDate !== undefined) {
|
||||
await validateReadingValue(
|
||||
meterId,
|
||||
data.readingDate || reading.readingDate,
|
||||
data.value ?? reading.value,
|
||||
readingId,
|
||||
'HT'
|
||||
);
|
||||
}
|
||||
if (data.valueNt !== undefined || data.readingDate !== undefined) {
|
||||
const ntVal = data.valueNt ?? reading.valueNt;
|
||||
if (ntVal !== undefined && ntVal !== null) {
|
||||
await validateReadingValue(
|
||||
meterId,
|
||||
data.readingDate || reading.readingDate,
|
||||
ntVal,
|
||||
readingId,
|
||||
'NT'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return prisma.meterReading.update({
|
||||
where: { id: readingId },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validiert, dass ein Zählerstand monoton steigend ist.
|
||||
* tariffLabel: 'HT' für Hochtarif/Eintarif, 'NT' für Niedertarif
|
||||
*/
|
||||
async function validateReadingValue(meterId: number, readingDate: Date, value: number, excludeReadingId?: number, tariffLabel: 'HT' | 'NT' = 'HT') {
|
||||
const existing = await prisma.meterReading.findMany({
|
||||
where: { meterId, ...(excludeReadingId ? { id: { not: excludeReadingId } } : {}) },
|
||||
orderBy: { readingDate: 'asc' },
|
||||
});
|
||||
|
||||
const fmtDate = (d: Date) => d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||||
const fmtVal = (v: number) => v.toLocaleString('de-DE');
|
||||
const label = tariffLabel === 'NT' ? 'NT-Zählerstand' : 'Zählerstand';
|
||||
|
||||
// Vergleichswert aus bestehendem Reading extrahieren
|
||||
const getVal = (r: typeof existing[0]) => tariffLabel === 'NT' ? (r.valueNt ?? 0) : r.value;
|
||||
|
||||
// Stand vor dem neuen Datum
|
||||
const before = [...existing].filter(r => r.readingDate <= readingDate).pop();
|
||||
if (before && value < getVal(before)) {
|
||||
throw new Error(`${label} (${fmtVal(value)}) darf nicht kleiner sein als der Stand vom ${fmtDate(before.readingDate)} (${fmtVal(getVal(before))})`);
|
||||
}
|
||||
|
||||
// Stand nach dem neuen Datum
|
||||
const after = existing.find(r => r.readingDate > readingDate);
|
||||
if (after && value > getVal(after)) {
|
||||
throw new Error(`${label} (${fmtVal(value)}) darf nicht größer sein als der spätere Stand vom ${fmtDate(after.readingDate)} (${fmtVal(getVal(after))})`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteMeterReading(meterId: number, readingId: number) {
|
||||
// Verify the reading belongs to the meter
|
||||
const reading = await prisma.meterReading.findFirst({
|
||||
|
||||
Reference in New Issue
Block a user