Jede Liste gehoert einem Spieler, CC-Guthaben wird umgewandelt

Ein Gruppen-CC wird fuer jeden Nutzer einzeln gerechnet: fuer den einen
kann er der gekaufte Platz sein, fuer den anderen ein Redesign seines
vorherigen CCs. Dafuer haengt die Liste jetzt an der Mitgliedschaft
statt am CC, und verschieben ordnet nur die eigene Liste.

Ein Redesign wird nie aus dem CC-Topf bezahlt. Wer sein CC-Guthaben
dafuer einsetzt, wandelt es um: ein Platz weniger, ein bezahltes
Redesign mehr. Damit entfaellt die Ausnahme, die sich bisher durch
Slot-Pruefung und Kettenrechnung zog.

Beim Start werden Buchungen einmalig an die Listen angeglichen.
This commit is contained in:
2026-09-22 12:21:53 +02:00
parent 9b024d343d
commit 1f4034b397
17 changed files with 1172 additions and 340 deletions
+103
View File
@@ -769,3 +769,106 @@ describe('Der eigene CC bleibt der eigene', () => {
);
});
});
describe('Ein Gruppen-CC zählt für jeden Nutzer einzeln', () => {
/// Womit ein bestimmter Spieler diesen CC bezahlt hat.
async function bezahltMit(characterId: number, playerId: number): Promise<string | undefined> {
const buchung = await db.prisma.creditLedger.findFirst({
where: { characterId, playerId, source: 'CONSUME', reversedBy: null },
orderBy: { id: 'desc' },
});
return buchung?.kind;
}
/// Die Liste dieses Spielers, von unten nach oben.
async function liste(playerId: number): Promise<string[]> {
const eigene = await db.prisma.characterMember.findMany({
where: { playerId },
orderBy: [{ lineageId: 'asc' }, { chainPosition: 'asc' }],
select: { lineageId: true, character: { select: { name: true } } },
});
return eigene.map((e) => `${e.lineageId.slice(0, 4)}:${e.character.name}`);
}
it('ist für den einen ein Platz und für den anderen ein Redesign', async () => {
const wirt = await spielerMitPaket('Zaehlwirt');
const gast = await spielerMitPaket('Zaehlgast');
const gruppe = await characters.createCharacter(wirt, { name: 'Gemeinsames Werk' }, ctx);
const eigener = await characters.createCharacter(gast, { name: 'Sein Erster' }, ctx);
await credits.bookRedesignPackage(gast, ctx);
await characters.addMember(
gruppe.character.id,
gast,
{ useKind: 'REDESIGN_PAID', predecessorId: eigener.character.id },
ctx,
);
assert.equal(await bezahltMit(gruppe.character.id, wirt), 'CC', 'für den Wirt sein Platz');
assert.equal(
await bezahltMit(gruppe.character.id, gast),
'REDESIGN_PAID',
'für den Gast das Redesign seines ersten CCs',
);
assert.equal(
await characters.richteBuchungenAus(),
0,
'beide Sichten sind in sich stimmig und bleiben unangetastet',
);
// Die Rechnung des Gastes: eine Liste mit zwei Einträgen.
const stand = await credits.getBalances(gast);
assert.equal(stand.CC, 0, 'ein Platz, eine Liste');
assert.equal(stand.REDESIGN_PAID, 1, 'von zwei Redesigns eines verbraucht');
assert.deepEqual(
(await liste(gast)).map((e) => e.split(':')[1]),
['Sein Erster', 'Gemeinsames Werk'],
'in seiner Liste steht der Gruppen-CC über seinem ersten',
);
});
it('lässt jeden für sich verschieben, ohne den anderen anzufassen', async () => {
const wirt = await spielerMitPaket('Schiebewirt', 2);
const gast = await spielerMitPaket('Schiebegast', 2);
const gruppe = await characters.createCharacter(wirt, { name: 'Die Bude' }, ctx);
const eigener = await characters.createCharacter(gast, { name: 'Gasts Erster' }, ctx);
await characters.addMember(gruppe.character.id, gast, { useKind: 'CC' }, ctx);
assert.equal(await bezahltMit(gruppe.character.id, gast), 'CC', 'erst ein eigener Platz');
// Der Gast reiht den Gruppen-CC in seine Liste ein: für ihn wird daraus
// ein Redesign seines ersten CCs.
await credits.bookRedesignPackage(gast, ctx);
await characters.moveInChain(
gruppe.character.id,
{
lineageId: (
await db.prisma.characterMember.findFirstOrThrow({
where: { playerId: gast, characterId: eigener.character.id },
})
).lineageId,
afterId: eigener.character.id,
forPlayerId: gast,
},
ctx,
);
assert.equal(
await bezahltMit(gruppe.character.id, gast),
'REDESIGN_PAID',
'in seiner Liste ist es jetzt ein Redesign',
);
assert.equal(
await bezahltMit(gruppe.character.id, wirt),
'CC',
'beim Wirt ändert sich dadurch nichts',
);
// Und der CC selbst bleibt aktiv - der Wirt spielt ihn weiter.
const danach = await db.prisma.character.findUniqueOrThrow({
where: { id: gruppe.character.id },
});
assert.equal(danach.status, 'ACTIVE');
});
});