Von Hand vergebenes CC-Guthaben ohne Redesign

Der Knopf trug bisher die Regel des Shops: ein CC-Paket ist ein Platz plus
ein Redesign. Fuer Einzelfaelle - jemand bekommt einen Platz als Ausgleich,
aus einer Absprache, weil etwas schiefging - war das zu viel.

"+ CC-Guthaben" schreibt jetzt nur den Platz gut. Wer ein volles Paket
nachtragen will, klickt zusaetzlich auf "+ Redesign-Paket". Der Abgleich mit
dem Shop bleibt unberuehrt: dort ist ein Paket weiterhin beides, und die
Regel dafuer steht unveraendert an einer Stelle.

In der Historie steht "CC-Guthaben (von Hand)" statt "CC-Paket" - eine eigene
Quelle, damit zu sehen ist, dass kein Kauf dahintersteht. Das Minus daneben
nimmt entsprechend diese Zugabe zurueck.
This commit is contained in:
2026-09-21 23:43:26 +02:00
parent e57bfbabc7
commit ae5c67eea6
10 changed files with 145 additions and 23 deletions
+8 -3
View File
@@ -4,9 +4,9 @@ import { CreditKind, CreditSource } from '@prisma/client';
import { requireAdmin, requireEditor } from '../auth/guard.js';
import {
bookAdjustment,
bookCcPackage,
bookKulanz,
bookRedesignPackage,
grantCcSlot,
removeLastGrant,
reverseGroup,
} from '../services/credits.js';
@@ -15,10 +15,15 @@ import { contextOf, idParam, optionalNote, parseWith } from './helpers.js';
const noteBody = z.object({ note: optionalNote });
export async function creditRoutes(app: FastifyInstance): Promise<void> {
/**
* Von Hand einen CC-Platz vergeben - ohne das Redesign, das in einem
* gekauften Paket steckt. Der Abgleich mit dem Shop bleibt davon
* unberührt: dort ist ein Paket weiterhin beides.
*/
app.post('/api/players/:id/credits/cc-package', { preHandler: requireEditor }, async (request) => {
const { id } = parseWith(idParam, request.params);
const body = parseWith(noteBody, request.body ?? {});
return bookCcPackage(id, contextOf(request), { note: body.note });
return grantCcSlot(id, contextOf(request), { note: body.note });
});
app.post(
@@ -50,7 +55,7 @@ export async function creditRoutes(app: FastifyInstance): Promise<void> {
{ preHandler: requireEditor },
async (request) => {
const { id } = parseWith(idParam, request.params);
return removeLastGrant(id, CreditSource.CC_PACKAGE, contextOf(request));
return removeLastGrant(id, CreditSource.CC_GRANT, contextOf(request));
},
);
+49 -5
View File
@@ -181,6 +181,47 @@ export async function bookCcPackage(
return { groupId, balances: await getBalances(playerId) };
}
/**
* Vergibt einen CC-Platz von Hand, ohne das Redesign aus einem Paket.
*
* Ein gekauftes CC-Paket bringt beides mit - so ist es im Shop, und so
* bucht es der Abgleich. Von Hand gibt es aber Einzelfälle, in denen jemand
* nur einen Platz bekommen soll: als Ausgleich, aus einer Absprache, weil
* etwas schiefging. Dafür ist das hier da, und deshalb trägt es eine eigene
* Quelle - in der Historie soll zu sehen sein, dass kein Kauf dahintersteht.
*/
export async function grantCcSlot(
playerId: number,
ctx: ActionContext,
options: { note?: string | undefined } = {},
): Promise<{ groupId: string; balances: Balances }> {
await requirePlayer(playerId);
const groupId = randomUUID();
await prisma.$transaction(async (tx) => {
await tx.creditLedger.create({
data: {
playerId,
kind: CreditKind.CC,
amount: 1,
source: CreditSource.CC_GRANT,
groupId,
note: options.note ?? null,
createdById: ctx.actorId,
},
});
await recordAudit(tx, ctx, {
action: 'credit.cc_grant',
targetType: 'player',
targetId: playerId,
details: { groupId, note: options.note ?? null },
});
});
return { groupId, balances: await getBalances(playerId) };
}
export async function bookRedesignPackage(
playerId: number,
ctx: ActionContext,
@@ -289,6 +330,7 @@ export async function bookAdjustment(
*/
/// Was sich über die Knöpfe auf der Spielerseite eintragen lässt.
const HANDEINTRAEGE = [
CreditSource.CC_GRANT,
CreditSource.CC_PACKAGE,
CreditSource.REDESIGN_PACKAGE,
CreditSource.KULANZ,
@@ -326,11 +368,13 @@ export async function removeLastGrant(
if (!juengste) {
const was =
source === CreditSource.CC_PACKAGE
? 'CC-Paket'
: source === CreditSource.REDESIGN_PACKAGE
? 'Redesign-Paket'
: 'Kulanz';
source === CreditSource.CC_GRANT
? 'CC-Guthaben'
: source === CreditSource.CC_PACKAGE
? 'CC-Paket'
: source === CreditSource.REDESIGN_PACKAGE
? 'Redesign-Paket'
: 'Kulanz';
throw conflict(
'NOTHING_TO_REMOVE',
`Es gibt kein von Hand eingetragenes ${was}, das sich zurücknehmen liesse. Aus einem Import gebuchte Pakete werden über die Historie storniert.`,