feat: Sitzungsverwaltung mit Stapel-Zuweisung und Ruecknahme

This commit is contained in:
vchuser
2026-07-28 14:59:46 +02:00
parent 8d50334915
commit f8e3a06d44
2 changed files with 208 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
// Reines Modul: kein window, kein document, kein localStorage.
import { specsCompatible } from './spec.js';
const STACK_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
/** @returns {{stacks: Array, entries: Array, nextEntryId: number}} */
export function createSession() {
return { stacks: [], entries: [], nextEntryId: 1 };
}
function nextStackId(session) {
const used = new Set(session.stacks.map((stack) => stack.id));
for (const letter of STACK_LETTERS) {
if (!used.has(letter)) return letter;
}
// Nach Z weiter mit A2, B2, ... - in der Praxis nie erreicht.
return `A${session.stacks.length}`;
}
/**
* Schlaegt vor, wohin ein Modul gehoert.
* Genau ein vertraeglicher Stapel -> Zuweisung.
* Keiner -> neuer Stapel.
* Mehrere -> mehrdeutig, der Nutzer entscheidet.
*/
export function proposeAssignment(session, spec) {
const candidates = session.stacks
.filter((stack) => specsCompatible(stack.spec, spec))
.map((stack) => stack.id);
if (candidates.length === 1) {
return { kind: 'match', stackId: candidates[0], candidates };
}
if (candidates.length === 0) {
return { kind: 'new', stackId: nextStackId(session), candidates: [] };
}
return { kind: 'ambiguous', stackId: null, candidates };
}
/** Bucht das Modul auf den angegebenen Stapel und legt ihn bei Bedarf an. */
export function commitAssignment(session, spec, source, stackId) {
let stack = session.stacks.find((candidate) => candidate.id === stackId);
if (!stack) {
stack = { id: stackId, spec, count: 0 };
session.stacks.push(stack);
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
} else {
// Ein spaeterer, vollstaendigerer Scan ergaenzt fehlende Felder des Stapels.
for (const field of ['capacityGb', 'formFactor', 'rank', 'speed', 'partNumber']) {
if (stack.spec[field] === null && spec[field] !== null) {
stack.spec[field] = spec[field];
}
}
}
stack.count += 1;
const entry = { entryId: session.nextEntryId++, spec, stackId, source };
session.entries.push(entry);
return entry;
}
function dropEmptyStacks(session) {
session.stacks = session.stacks.filter((stack) => stack.count > 0);
}
/** Nimmt den zuletzt erfassten Eintrag zurueck. */
export function undoLast(session) {
const entry = session.entries.pop();
if (!entry) return null;
const stack = session.stacks.find((candidate) => candidate.id === entry.stackId);
if (stack) stack.count -= 1;
dropEmptyStacks(session);
return entry;
}
/** Ordnet einen bereits erfassten Eintrag einem anderen Stapel zu. */
export function moveEntry(session, entryId, stackId) {
const entry = session.entries.find((candidate) => candidate.entryId === entryId);
if (!entry || entry.stackId === stackId) return;
const from = session.stacks.find((candidate) => candidate.id === entry.stackId);
if (from) from.count -= 1;
let to = session.stacks.find((candidate) => candidate.id === stackId);
if (!to) {
to = { id: stackId, spec: entry.spec, count: 0 };
session.stacks.push(to);
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
}
to.count += 1;
entry.stackId = stackId;
dropEmptyStacks(session);
}
/** Entfernt einen einzelnen Eintrag aus der Sitzung. */
export function removeEntry(session, entryId) {
const index = session.entries.findIndex((candidate) => candidate.entryId === entryId);
if (index === -1) return;
const [entry] = session.entries.splice(index, 1);
const stack = session.stacks.find((candidate) => candidate.id === entry.stackId);
if (stack) stack.count -= 1;
dropEmptyStacks(session);
}