Files
ProxLedger/src/server/queries/dashboard.ts
T
florian.wildeandClaude Opus 5 558dfea34c feat(rechnungen): Laufzeiten bestaetigen, Rechnungen erfassen und Status setzen (#008)
Rechnungen mit Positionen: gleiche Nummer ergibt eine Sammelrechnung ueber mehrere
Laufzeiten. PDF-Upload mit Inhaltspruefung und Ablage unter der Pruefsumme,
Haekchen direkt in der Tabelle, Filter nach Status, Kunde und Jahr. 78 Tests.

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

121 lines
4.0 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. */
export async function offeneAufgaben() {
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;
}