Codetreffer ueberstimmt keinen Widerspruch der technischen Angaben mehr
Ein gelesener Rohcode ist nicht garantiert eine Teilenummer: teilen sich zwei technisch verschiedene Module z.B. denselben Los-Tag, hat der bisherige Codeabgleich sie ohne Rueckfrage auf denselben Stapel gezogen. proposeAssignment verlangt fuer einen Codekandidaten jetzt zusaetzlich specsCompatible; widerspricht der einzige Treffer, faellt die Zuordnung auf den Vergleich der technischen Angaben zurueck und im Zweifel auf einen eigenen Stapel. TDD: Test zuerst rot gesehen (Los-Tag-Fall aus der Beanstandung), dann die Filterung ergaenzt.
This commit is contained in:
+56
-20
@@ -57,12 +57,22 @@ function normalizedCodeSet(codes) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ergaenzt die Menge der bei einem Stapel bekannten Barcode-Inhalte um die
|
||||
* neu gelesenen Codes (z.B. Teile- und Seriennummer desselben Etiketts).
|
||||
* Die Codemenge eines Stapels ist kein angesammelter, sondern ein
|
||||
* abgeleiteter Wert: sie ergibt sich aus den Codes der ihm *aktuell*
|
||||
* zugeordneten Eintraege - genau wie stack.count (siehe loadSession in
|
||||
* src/storage.js, das denselben Ansatz beim Laden verfolgt). Ein Code
|
||||
* verschwindet damit automatisch aus einem Stapel, sobald kein Eintrag ihn
|
||||
* mehr mitbringt (nach moveEntry, removeEntry oder undoLast) - anstatt sich
|
||||
* dauerhaft am Stapel festzusetzen.
|
||||
*/
|
||||
function addCodes(stack, codes) {
|
||||
const set = new Set(stack.codes);
|
||||
for (const code of normalizedCodeSet(codes)) set.add(code);
|
||||
function recomputeStackCodes(session, stackId) {
|
||||
const stack = session.stacks.find((candidate) => candidate.id === stackId);
|
||||
if (!stack) return;
|
||||
const set = new Set();
|
||||
for (const entry of session.entries) {
|
||||
if (entry.stackId !== stackId) continue;
|
||||
for (const code of entry.codes ?? []) set.add(code);
|
||||
}
|
||||
stack.codes = [...set];
|
||||
}
|
||||
|
||||
@@ -72,17 +82,25 @@ function addCodes(stack, codes) {
|
||||
* Ein bekannter Barcode-Inhalt hat Vorrang vor dem Vergleich der technischen
|
||||
* Angaben, denn ein Barcode ist exakt gelesen, waehrend abgeleitete Angaben
|
||||
* fehlerbehaftet sein koennen (siehe README, Abschnitt Barcode-Erkennung).
|
||||
* Genau ein Stapel mit einem der gescannten Codes -> Zuweisung, auch bei
|
||||
* unvollstaendigen technischen Angaben. Mehrere -> mehrdeutig, der Nutzer
|
||||
* entscheidet. Kennt kein Stapel einen der Codes, gilt unveraendert die
|
||||
* bisherige Regel: genau ein vertraeglicher Stapel mit gemeinsamer
|
||||
* Grundlage -> Zuweisung, keiner -> neuer Stapel, mehrere -> mehrdeutig.
|
||||
* Ein Codetreffer allein genuegt aber nicht: nicht jeder gelesene Rohcode ist
|
||||
* eine Teilenummer - steht neben ihr noch eine Los- oder Wiederverkaeufer-
|
||||
* Kennung auf dem Etikett, kann die zwei technisch verschiedene Module
|
||||
* verbinden. Ein Stapel ist deshalb ueber einen Code nur dann Kandidat, wenn
|
||||
* die technischen Angaben zusaetzlich nicht widersprechen (specsCompatible);
|
||||
* der Code entscheidet dann innerhalb dessen, was ohnehin vertraeglich ist,
|
||||
* er darf einen Widerspruch nicht ueberstimmen. Genau ein solcher Kandidat
|
||||
* -> Zuweisung, auch bei unvollstaendigen technischen Angaben. Mehrere ->
|
||||
* mehrdeutig, der Nutzer entscheidet. Kennt kein Stapel einen der Codes ohne
|
||||
* Widerspruch, gilt unveraendert die bisherige Regel: genau ein vertraeglicher
|
||||
* Stapel mit gemeinsamer Grundlage -> Zuweisung, keiner -> neuer Stapel,
|
||||
* mehrere -> mehrdeutig.
|
||||
*/
|
||||
export function proposeAssignment(session, spec, codes = []) {
|
||||
const scannedCodes = normalizedCodeSet(codes);
|
||||
if (scannedCodes.size > 0) {
|
||||
const codeCandidates = session.stacks
|
||||
.filter((stack) => Array.isArray(stack.codes) && stack.codes.some((code) => scannedCodes.has(code)))
|
||||
.filter((stack) => specsCompatible(stack.spec, spec))
|
||||
.map((stack) => stack.id);
|
||||
|
||||
if (codeCandidates.length === 1) {
|
||||
@@ -111,9 +129,10 @@ export function proposeAssignment(session, spec, codes = []) {
|
||||
/**
|
||||
* Bucht das Modul auf den angegebenen Stapel und legt ihn bei Bedarf an.
|
||||
* `codes` sind die bei diesem Modul gelesenen rohen Barcode-Zeichenketten
|
||||
* (z.B. Teile- und Seriennummer); sie werden der Code-Menge des Stapels
|
||||
* hinzugefuegt, damit ein spaeteres Modul mit demselben Inhalt darueber
|
||||
* zugeordnet werden kann (siehe proposeAssignment).
|
||||
* (z.B. Teile- und Seriennummer); sie werden am Eintrag gespeichert, aus dem
|
||||
* sich die Code-Menge des Stapels ableitet (siehe recomputeStackCodes), damit
|
||||
* ein spaeteres Modul mit demselben Inhalt darueber zugeordnet werden kann
|
||||
* (siehe proposeAssignment).
|
||||
*/
|
||||
export function commitAssignment(session, spec, source, stackId, codes = []) {
|
||||
let stack = session.stacks.find((candidate) => candidate.id === stackId);
|
||||
@@ -122,7 +141,6 @@ export function commitAssignment(session, spec, source, stackId, codes = []) {
|
||||
session.stacks.push(stack);
|
||||
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
|
||||
} else {
|
||||
if (!Array.isArray(stack.codes)) stack.codes = [];
|
||||
// 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) {
|
||||
@@ -131,13 +149,20 @@ export function commitAssignment(session, spec, source, stackId, codes = []) {
|
||||
}
|
||||
}
|
||||
|
||||
addCodes(stack, codes);
|
||||
|
||||
stack.count += 1;
|
||||
// entry.spec ist das Protokoll dessen, was tatsaechlich gescannt wurde, und muss
|
||||
// unabhaengig vom Stapel-Spec bleiben - eigene Kopie, kein geteiltes Objekt.
|
||||
const entry = { entryId: session.nextEntryId++, spec: { ...spec }, stackId, source };
|
||||
// entry.codes haelt fest, welche Rohcodes bei diesem Eintrag gelesen wurden -
|
||||
// die Grundlage, aus der recomputeStackCodes die Codemenge des Stapels ableitet.
|
||||
const entry = {
|
||||
entryId: session.nextEntryId++,
|
||||
spec: { ...spec },
|
||||
stackId,
|
||||
source,
|
||||
codes: [...normalizedCodeSet(codes)],
|
||||
};
|
||||
session.entries.push(entry);
|
||||
recomputeStackCodes(session, stackId);
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -157,6 +182,9 @@ export function undoLast(session) {
|
||||
if (!entry) return null;
|
||||
const stack = session.stacks.find((candidate) => candidate.id === entry.stackId);
|
||||
if (stack) stack.count -= 1;
|
||||
// Der zurueckgenommene Eintrag zaehlt nicht mehr zu diesem Stapel - seine
|
||||
// Codes duerfen deshalb auch keinen kuenftigen Treffer mehr begruenden.
|
||||
recomputeStackCodes(session, entry.stackId);
|
||||
dropEmptyStacks(session);
|
||||
return entry;
|
||||
}
|
||||
@@ -166,19 +194,24 @@ 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);
|
||||
const fromId = entry.stackId;
|
||||
const from = session.stacks.find((candidate) => candidate.id === fromId);
|
||||
if (from) from.count -= 1;
|
||||
|
||||
let to = session.stacks.find((candidate) => candidate.id === stackId);
|
||||
if (!to) {
|
||||
// entry selbst fuehrt keine Codes (nur Stapel tun das) - ein durch
|
||||
// Umsortieren neu entstandener Stapel startet deshalb ohne bekannte Codes.
|
||||
to = { id: stackId, spec: { ...entry.spec }, count: 0, codes: [] };
|
||||
session.stacks.push(to);
|
||||
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
to.count += 1;
|
||||
entry.stackId = stackId;
|
||||
// Die Codemenge beider betroffenen Stapel ist abgeleitet (siehe
|
||||
// recomputeStackCodes) - nach dem Umhaengen des Eintrags muss sie fuer den
|
||||
// alten wie den neuen Stapel neu bestimmt werden, sonst bliebe der Code des
|
||||
// umsortierten Eintrags faelschlich am alten Stapel haengen.
|
||||
recomputeStackCodes(session, fromId);
|
||||
recomputeStackCodes(session, stackId);
|
||||
dropEmptyStacks(session);
|
||||
}
|
||||
|
||||
@@ -189,5 +222,8 @@ export function removeEntry(session, entryId) {
|
||||
const [entry] = session.entries.splice(index, 1);
|
||||
const stack = session.stacks.find((candidate) => candidate.id === entry.stackId);
|
||||
if (stack) stack.count -= 1;
|
||||
// Siehe moveEntry: der entfernte Eintrag darf seinen Code nicht laenger am
|
||||
// Stapel hinterlassen.
|
||||
recomputeStackCodes(session, entry.stackId);
|
||||
dropEmptyStacks(session);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user