Files
ProxLedger/src/server/queries/dashboard.ts
T
florian.wildeandClaude Opus 5 4c65c9ddab perf(alerts): Warnungen sammelweise anlegen, Dark-Mode und Mobilansicht verbessern (#015)
Lasttest mit 200 Hosts und 365.000 Reports: Auswertung der Warnregeln von 3813
auf 293 ms. Logo im dunklen Design sichtbar gemacht, Tabellen blenden auf
schmalen Bildschirmen Spalten aus, Aufgaben-Karte begrenzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 23:27:23 +02:00

125 lines
4.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { prisma } from "@/lib/db";
import { meldungsGrenze } from "./hosts";
/**
* Hosts nach Ablaufdatum mit der Laufzeit, die zu diesem Ablauf gehört.
* Gezeigt wird bewusst die Verlängerung (die Laufzeit, die am `nextduedate` beginnt),
* denn deren Rechnungen sind offen – nicht die der laufenden Periode.
*/
export async function hostsNachAblauf(jetzt: Date = new Date()) {
const hosts = await prisma.host.findMany({
where: { subNextduedate: { not: null } },
orderBy: { subNextduedate: "asc" },
include: {
kunde: { select: { id: true, name: true } },
laufzeiten: {
where: { status: "BESTAETIGT" },
orderBy: { start: "desc" },
include: {
proxmoxPosition: { include: { rechnung: { select: { nummer: true, bezahlt: true } } } },
kundenPosition: { include: { rechnung: { select: { erstellt: true, bezahlt: true } } } },
},
},
},
});
const grenze = meldungsGrenze(jetzt);
return hosts.map((host) => {
const ablauf = host.subNextduedate;
const verlaengerung = host.laufzeiten.find(
(laufzeit) => ablauf && laufzeit.start.getTime() === ablauf.getTime(),
);
const laufend = host.laufzeiten[0];
const laufzeit = verlaengerung ?? laufend;
return {
id: host.id,
hostname: host.hostname,
kunde: host.kunde,
subKey: host.subKey,
nextduedate: ablauf,
meldetNicht: !host.letzterReportAm || host.letzterReportAm < grenze,
einkaufBezahlt: laufzeit?.proxmoxPosition ? laufzeit.proxmoxPosition.rechnung.bezahlt : null,
verkaufErstellt: laufzeit?.kundenPosition?.rechnung.erstellt ?? false,
verkaufBezahlt: laufzeit?.kundenPosition?.rechnung.bezahlt ?? false,
};
});
}
/**
* Offene Rechnungspunkte als Aufgabenliste fürs Dashboard.
* Begrenzt, weil die Karte sonst bei vielen Hosts endlos lang wird – die vollständige
* Liste steht unter „Rechnungen“.
*/
export async function offeneAufgaben(grenze = 12) {
const laufzeiten = await prisma.laufzeit.findMany({
where: {
status: "BESTAETIGT",
OR: [
{ proxmoxPosition: { rechnung: { bezahlt: false } } },
{ kundenPosition: null },
{ kundenPosition: { rechnung: { erstellt: false } } },
],
},
orderBy: { start: "asc" },
include: {
host: { select: { id: true, hostname: true, kunde: { select: { name: true } } } },
proxmoxPosition: { include: { rechnung: true } },
kundenPosition: { include: { rechnung: true } },
},
});
const aufgaben: {
id: string;
titel: string;
detail: string;
betrag: number | null;
faelligAm: Date | null;
dringlichkeit: "gefahr" | "warnung";
badgeText: string;
href: string;
start: Date;
ende: Date;
aktion: { laufzeitId: string; hostId: string; feld: "einkaufBezahlt" | "verkaufErstellt" };
}[] = [];
for (const laufzeit of laufzeiten) {
const basis = `${laufzeit.host.hostname} · ${laufzeit.host.kunde?.name ?? "Nicht zugeordnet"}`;
const einkauf = laufzeit.proxmoxPosition?.rechnung;
if (einkauf && !einkauf.bezahlt) {
aufgaben.push({
id: `${laufzeit.id}-einkauf`,
titel: `Proxmox-Rechnung ${einkauf.nummer} bezahlen`,
detail: basis,
betrag: Number(laufzeit.proxmoxPosition?.nettoBetrag ?? 0),
faelligAm: einkauf.faelligAm,
dringlichkeit: "gefahr" as const,
badgeText: "Einkauf",
href: `/rechnungen/${laufzeit.id}`,
start: laufzeit.start,
ende: laufzeit.ende,
aktion: { laufzeitId: laufzeit.id, hostId: laufzeit.hostId, feld: "einkaufBezahlt" as const },
});
}
if (einkauf && !laufzeit.kundenPosition?.rechnung.erstellt) {
aufgaben.push({
id: `${laufzeit.id}-verkauf`,
titel: "Kundenrechnung erstellen",
detail: basis,
betrag: null,
faelligAm: null,
dringlichkeit: "warnung" as const,
badgeText: "Verkauf",
href: `/rechnungen/${laufzeit.id}`,
start: laufzeit.start,
ende: laufzeit.ende,
aktion: { laufzeitId: laufzeit.id, hostId: laufzeit.hostId, feld: "verkaufErstellt" as const },
});
}
}
return { aufgaben: aufgaben.slice(0, grenze), gesamt: aufgaben.length };
}