Fachlogik fuer Guthaben, CC-Lebenszyklus und Import

Die beiden zentralen Regeln stehen jeweils an genau einer Stelle:

Ein CC-Paket erzeugt immer CC +1 und REDESIGN_PAID +1. Die Zeilen dafuer baut
buildPackageRows, das sowohl die manuelle Eingabe als auch der Import nutzen -
so koennen beide Wege nicht auseinanderlaufen.

Ein CC kommt nur dann gratis zurueck, wenn er wegen Spieler-Inaktivitaet
entfernt wurde. Ein reworkter CC existiert im Original nicht mehr und kostet
deshalb ein Redesign- oder CC-Guthaben.

Ueberzogene Toepfe werden zugelassen statt blockiert; die Aktion meldet das
ueber ein overdrawn-Flag zurueck. Storniert wird per Gegenbuchung, nie durch
Loeschen. Der Import berechnet den Plan vorab und dedupliziert ueber die
Bestell-ID, sowohl gegen bereits gebuchte als auch innerhalb einer Datei.
This commit is contained in:
2026-09-18 11:37:31 +02:00
parent 3d2b87297e
commit f3e31cdd17
5 changed files with 1379 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
import type { Prisma } from '@prisma/client';
import { prisma } from '../db.js';
export interface ActionContext {
// null fuer Vorgaenge ohne angemeldeten Account, etwa ein fehlgeschlagener Login.
actorId: number | null;
actorName: string;
ip?: string | undefined;
}
export interface AuditEntry {
action: string;
targetType: string;
targetId?: string | number | null;
details?: Prisma.InputJsonValue;
}
type Client = Pick<typeof prisma, 'auditLog'>;
export async function recordAudit(
client: Client,
ctx: ActionContext,
entry: AuditEntry,
): Promise<void> {
await client.auditLog.create({
data: {
actorUserId: ctx.actorId,
actorName: ctx.actorName,
action: entry.action,
targetType: entry.targetType,
targetId: entry.targetId == null ? null : String(entry.targetId),
details: entry.details ?? undefined,
ip: ctx.ip ?? null,
},
});
}