Die Stelle in der Kette bestimmt die Buchung

Bisher blieb eine Buchung, wie sie beim Anlegen entstand - auch wenn der
Eintrag danach an eine ganz andere Stelle wanderte. Damit stand ein CC, der
in eine Kette geschoben wurde, weiter als gekauftes Paket im Konto, obwohl er
inzwischen ein Redesign war.

Jetzt folgt die Buchung der Position: Das unterste Glied ist der gekaufte CC
und verbraucht ein CC-Paket, alles darueber sind Redesigns. Wandert ein
Eintrag, wandert seine Bedeutung mit - in beide Richtungen. Wer mitten in eine
Kette rutscht, wird zum Redesign und gibt sein CC-Guthaben zurueck; wer an den
Anfang kommt oder herausgeloest wird, belegt einen eigenen Platz und kostet
wieder ein Paket.

Umgebucht wird ueber eine Gegenbuchung, nicht durch Ueberschreiben. Reicht das
Guthaben nicht, geht das Konto ins Minus und zeigt es an - wie bei jedem
Vorab-CC. Zwischen bezahltem Redesign und Kulanz wird nicht gewechselt: das
ist eine Entscheidung, keine Folge der Reihenfolge. Eintraege ohne Buchung
bleiben unangetastet.

Dabei zaehlt die Buchung dessen, dem die Kette gehoert - der Spieler, der
unten steht. Bei einem fremden Gruppen-CC in seiner Kette ist das seine
Beitrittsbuchung; die des Erstellers bleibt, denn fuer den ist es sein eigener
Platz in seiner eigenen Kette.

Damit liess sich der Gruppen-CC ueberhaupt erst einreihen: Der Besitzer-Check
fragte nach dem Ersteller, und der ist bei einem Gruppen-CC ein anderer als
der, dessen Seite man gerade ordnet. Jetzt entscheidet die Beteiligung - wer
an beidem beteiligt war, darf einreihen, ein Unbeteiligter nicht. Fehler beim
Ziehen stehen ausserdem neben den Ketten statt am Seitenkopf: wer unten zieht,
sieht dort oben nichts.
This commit is contained in:
2026-09-21 18:38:11 +02:00
parent 2b78a972bc
commit 2d1c008c0a
5 changed files with 432 additions and 27 deletions
+26 -6
View File
@@ -1,5 +1,6 @@
import { useState } from 'react';
import MemberList from './MemberList';
import Notice from './Notice';
import { ApiError, api } from '../lib/api';
import { REASON_LABEL, formatDate, rueckkehrArt } from '../lib/format';
import type { Character } from '../lib/types';
@@ -120,6 +121,9 @@ export default function ChainView({
}) {
const [ziehe, setZiehe] = useState<Character | null>(null);
const [ueber, setUeber] = useState<string | null>(null);
// Eine Meldung zum Ziehen gehört neben die Ketten, nicht an den Seitenkopf:
// wer unten zieht, sieht dort oben nichts.
const [meldung, setMeldung] = useState<string | null>(null);
const { ketten, vergangen } = baueKetten(characters, playerId);
@@ -147,24 +151,26 @@ export default function ChainView({
if (index === zielIndex || index === zielIndex - 1) return;
}
setMeldung(null);
try {
await api(`/api/characters/${bewegt.id}/move`, {
method: 'POST',
body: { lineageId, afterId },
body: { lineageId, afterId, forPlayerId: playerId },
});
onMoved();
} catch (err) {
onError(err instanceof ApiError ? err.message : 'Verschieben fehlgeschlagen');
setMeldung(err instanceof ApiError ? err.message : 'Verschieben fehlgeschlagen');
}
};
const herausloesen = async (character: Character) => {
if (!window.confirm(`"${character.name}" aus seiner Kette lösen und allein stellen?`)) return;
setMeldung(null);
try {
await api(`/api/characters/${character.id}/detach`, { method: 'POST', body: {} });
onMoved();
} catch (err) {
onError(err instanceof ApiError ? err.message : 'Herauslösen fehlgeschlagen');
setMeldung(err instanceof ApiError ? err.message : 'Herauslösen fehlgeschlagen');
}
};
@@ -194,13 +200,27 @@ export default function ChainView({
<div>
<h2>CCs ({characters.length})</h2>
<p className="muted small">
Jede Spalte ist ein Platz, von unten nach oben gelesen: unten der erste CC, darüber
seine Redesigns.
{canEdit && ' Einträge lassen sich mit der Maus an ihren Platz ziehen - auch mitten in eine Kette oder in eine andere.'}
Jede Spalte ist ein Platz, von unten nach oben gelesen: unten der gekaufte CC,
darüber seine Redesigns.
{canEdit && (
<>
{' '}
Einträge lassen sich mit der Maus an ihren Platz ziehen - auch mitten in eine
Kette oder in eine andere. <strong>Die Stelle bestimmt die Buchung:</strong> was
unten steht, kostet ein CC-Paket, alles darüber ein Redesign. Beim Verschieben
wird entsprechend umgebucht.
</>
)}
</p>
</div>
</div>
{meldung && (
<Notice kind="error" onDismiss={() => setMeldung(null)}>
{meldung}
</Notice>
)}
<div className="chains">
{ketten.map((kette, index) => {
const aktiv = kette.glieder.find((c) => c.status === 'ACTIVE');