feat(laufzeiten): anlegen, bearbeiten und zusammenfuehren (#043)

Laufzeiten entstanden bisher nur als Vorschlag aus dem Agenten-Report;
bestaetigen oder verwerfen war alles. Anlegen von Hand gab es als Aktion ohne
Oberflaeche, Aendern und Zusammenfuehren gar nicht - obwohl der Doppelfall
zwangslaeufig auftritt, sobald das gemeldete Ablaufdatum von der Bestellung
abweicht.

Jetzt: Formular zum Anlegen auf der Rechnungsseite (Key vorgeschlagen, Ende
aus dem Beginn vorbelegt), Bearbeiten von Key, Zeitraum und Notiz im
Laufzeit-Detail, und Zusammenfuehren zweier Laufzeiten desselben Hosts. Die
aufgeloeste gibt ihre Rechnungen ab, sofern das Ziel dort nichts hat; sonst
bricht der Vorgang mit Erklaerung ab - zwei Einkaufsrechnungen auf einem
Lizenzjahr waeren nicht mehr zuzuordnen.

Dazu die Kundenansicht: Laufzeit und Host verlinkt, Marge und Rabatt je Zeile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-03 20:57:07 +02:00
co-authored by Claude Opus 5
parent f076f49631
commit 83320f0443
22 changed files with 738 additions and 15 deletions
+35 -1
View File
@@ -11,6 +11,8 @@ import { formatDatum, formatEuro } from "@/lib/format";
import { laufzeitBestaetigenAktion, laufzeitVerwerfenAktion } from "@/server/actions/rechnungen";
import { einstellungen, faelligkeitsVorschlag } from "@/server/einstellungen";
import { laufzeitDetail } from "@/server/queries/rechnungen";
import { LaufzeitBearbeiten } from "@/components/rechnungen/LaufzeitBearbeiten";
import { prisma } from "@/lib/db";
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
@@ -33,7 +35,22 @@ export default async function LaufzeitSeite({ params }: { params: Promise<{ id:
const marge = einkaufBetrag !== null && verkaufBetrag !== null ? verkaufBetrag - einkaufBetrag : null;
// Vorgaben aus den Einstellungen füllen leere Felder vor (#025).
const [vorgaben, faelligVorschlag] = await Promise.all([einstellungen(), faelligkeitsVorschlag()]);
const [vorgaben, faelligVorschlag, nachbarn] = await Promise.all([
einstellungen(),
faelligkeitsVorschlag(),
prisma.laufzeit.findMany({
where: { hostId: laufzeit.host.id, id: { not: laufzeit.id } },
orderBy: { start: "desc" },
select: {
id: true,
key: true,
start: true,
ende: true,
proxmoxPositionId: true,
kundenPositionId: true,
},
}),
]);
const verkaufVorschlag =
verkaufBetrag ??
(einkaufBetrag === null ? null : Math.round(einkaufBetrag * (1 + vorgaben.margeProzent / 100) * 100) / 100);
@@ -127,6 +144,23 @@ export default async function LaufzeitSeite({ params }: { params: Promise<{ id:
{marge === null ? "–" : formatEuro(marge)}
</dd>
</dl>
<hr />
<LaufzeitBearbeiten
laufzeitId={laufzeit.id}
hostId={laufzeit.host.id}
schluessel={laufzeit.key}
start={laufzeit.start.toISOString().slice(0, 10)}
ende={laufzeit.ende.toISOString().slice(0, 10)}
notizen={laufzeit.notizen}
nachbarn={nachbarn.map((nachbar) => ({
id: nachbar.id,
zeitraum: `${formatDatum(nachbar.start)} – ${formatDatum(nachbar.ende)}`,
key: nachbar.key,
hatEinkauf: nachbar.proxmoxPositionId !== null,
hatVerkauf: nachbar.kundenPositionId !== null,
}))}
/>
</Card>
{einkauf && einkauf.positionen.length > 1 ? (