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>
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
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 };
|
||
}
|