MBO-Tech-IT-Webseite/modules/07-kpi-dashboard/files/app/api/admin/statistik/route.ts

176 lines
5.6 KiB
TypeScript

import { NextResponse } from "next/server";
import { createServiceClient } from "@/lib/supabase";
import { requireAdmin } from "@/lib/admin-auth";
export const dynamic = "force-dynamic";
export async function GET() {
const check = await requireAdmin();
if (check instanceof NextResponse) return check;
const db = createServiceClient();
const heute = new Date();
heute.setHours(0, 0, 0, 0);
const heuteISO = heute.toISOString().slice(0, 10);
// Fenster: 12 Monate zurück für Statistiken
const vor12Monaten = new Date(heute);
vor12Monaten.setMonth(vor12Monaten.getMonth() - 12);
const vor12ISO = vor12Monaten.toISOString().slice(0, 10);
// Fenster: 3 Monate voraus für Planungsübersicht
const in3Monaten = new Date(heute);
in3Monaten.setMonth(in3Monaten.getMonth() + 3);
in3Monaten.setDate(in3Monaten.getDate() + 1);
const in3ISO = in3Monaten.toISOString().slice(0, 10);
// ── Alle Anfragen (Status + Datum) ─────────────────────────────────────
const { data: anfragen } = await db
.from("anfragen")
.select("id, status, created_at, firma, telefon, email")
.order("created_at", { ascending: false });
// ── Alle Positionen (Statistik: letzte 12 Monate) ──────────────────────
const { data: allePositionen } = await db
.from("anfragen_positionen")
.select(
"id, anfrage_id, maschine_name, maschine_kategorie, mietbeginn, mietende, gesamt_tage, tagessatz, lieferung"
)
.gte("mietbeginn", vor12ISO)
.order("mietbeginn");
// ── Planungs-Positionen (heute bis +3 Monate) ──────────────────────────
const { data: planPositionen } = await db
.from("anfragen_positionen")
.select(
"id, anfrage_id, maschine_name, maschine_kategorie, mietbeginn, mietende, gesamt_tage, tagessatz"
)
.lte("mietbeginn", in3ISO)
.gte("mietende", heuteISO)
.order("mietbeginn");
const anfrageMap = new Map(
(anfragen ?? []).map((a) => [a.id, a])
);
// ── KPI-Berechnung (nur abgeschlossene Mietvorgänge zählen, die zuvor bestätigt waren) ──
// Lade Audit Log um zu verifizieren: bestätigt → abgeschlossenen Workflow
const { data: auditLogs } = await db
.from("anfrage_status_audit")
.select("anfrage_id, status_zu");
// Map: anfrage_id → hat bestätigt Status durchlaufen?
const hatBestaetigt = new Map<string, boolean>();
(auditLogs ?? []).forEach((log: any) => {
if (log.status_zu === "bestaetigt") {
hatBestaetigt.set(log.anfrage_id, true);
}
});
const bestaetigtIds = new Set(
(anfragen ?? [])
.filter((a) => a.status === "abgeschlossen" && hatBestaetigt.has(a.id))
.map((a) => a.id)
);
const kpiPositionen = (allePositionen ?? []).filter((p) =>
bestaetigtIds.has(p.anfrage_id)
);
const umsatzGesamt = kpiPositionen.reduce(
(sum, p) => sum + (p.tagessatz != null ? p.tagessatz * p.gesamt_tage : 0),
0
);
const mietTageGesamt = kpiPositionen.reduce(
(sum, p) => sum + (p.gesamt_tage ?? 0),
0
);
// ── Auslastung pro Maschine ─────────────────────────────────────────────
const maschinenMap: Record<
string,
{ name: string; kategorie: string; tage: number; umsatz: number; anfragen: number }
> = {};
for (const p of allePositionen ?? []) {
if (!bestaetigtIds.has(p.anfrage_id)) continue;
const key = p.maschine_name;
if (!maschinenMap[key]) {
maschinenMap[key] = {
name: p.maschine_name,
kategorie: p.maschine_kategorie ?? "",
tage: 0,
umsatz: 0,
anfragen: 0,
};
}
maschinenMap[key].tage += p.gesamt_tage ?? 0;
maschinenMap[key].umsatz +=
p.tagessatz != null ? p.tagessatz * p.gesamt_tage : 0;
maschinenMap[key].anfragen += 1;
}
const maschinenStats = Object.values(maschinenMap).sort(
(a, b) => b.tage - a.tage
);
// ── Monatliche Entwicklung (letzte 6 Monate) ───────────────────────────
const monatsStats: {
monat: string;
label: string;
anfragen: number;
tage: number;
umsatz: number;
}[] = [];
for (let i = 5; i >= 0; i--) {
const d = new Date(heute);
d.setMonth(d.getMonth() - i);
const monat = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`;
const label = d.toLocaleDateString("de-DE", { month: "short", year: "2-digit" });
const mPositionen = (allePositionen ?? []).filter(
(p) =>
bestaetigtIds.has(p.anfrage_id) &&
p.mietbeginn?.startsWith(monat)
);
monatsStats.push({
monat,
label,
anfragen: mPositionen.length,
tage: mPositionen.reduce((s, p) => s + (p.gesamt_tage ?? 0), 0),
umsatz: mPositionen.reduce(
(s, p) =>
s + (p.tagessatz != null ? p.tagessatz * p.gesamt_tage : 0),
0
),
});
}
// ── Planungs-Daten mit Anfragestatus anreichern ─────────────────────────
const planDaten = (planPositionen ?? []).map((p) => {
const anfrage = anfrageMap.get(p.anfrage_id);
return {
...p,
anfrage_status: anfrage?.status ?? "offen",
firma: anfrage?.firma ?? "",
};
});
return NextResponse.json({
kpi: {
anfragenGesamt: anfragen?.length ?? 0,
bestaetigtGesamt: bestaetigtIds.size,
umsatzGesamt,
mietTageGesamt,
},
maschinenStats,
monatsStats,
planDaten,
heuteISO,
in3ISO,
});
}