163 lines
4.9 KiB
JavaScript
163 lines
4.9 KiB
JavaScript
/**
|
|
* Fachliche Logik des GTIN-Generators.
|
|
*
|
|
* Diese Datei enthaelt ausschliesslich die Nummernkreis-Regeln, die
|
|
* Pruefziffernberechnung und die Validierung. Sie kennt weder DOM noch
|
|
* Oberflaeche und ist deshalb direkt testbar.
|
|
*
|
|
* Regeln (belegte Formate):
|
|
* 3 Ziffern -> links mit 0 auf 4 Stellen auffuellen -> Praefix 290 -> EAN-8
|
|
* 4 Ziffern -> Praefix 290 -> EAN-8
|
|
* 7 Ziffern -> Praefix 20090 -> EAN-13
|
|
* alles andere ist ungueltig.
|
|
*/
|
|
|
|
/** @typedef {'EAN-8' | 'EAN-13'} GtinTyp */
|
|
|
|
/**
|
|
* @typedef {object} GtinErgebnis
|
|
* @property {string} eingabe normalisierte Artikelnummer (3, 4 oder 7 Stellen)
|
|
* @property {string} artikelblock Artikelblock mit fuehrender Null (4 Stellen)
|
|
* @property {string} praefix Nummernkreis-Praefix
|
|
* @property {string} basis GTIN ohne Pruefziffer
|
|
* @property {string} pruefziffer berechnete Pruefziffer
|
|
* @property {string} gtin vollstaendige GTIN inklusive Pruefziffer
|
|
* @property {GtinTyp} typ Barcode-Typ
|
|
* @property {number} laenge GTIN-Laenge (8 oder 13)
|
|
*/
|
|
|
|
/** Fehlertext fuer die Oberflaeche (bewusst genau ein Text). */
|
|
export const FEHLERTEXT =
|
|
'Keine gültige Artikelnummer. Erwartet werden 3, 4 oder 7 Stellen.';
|
|
|
|
/** Praefix des kurzen Nummernkreises (EAN-8). */
|
|
export const PRAEFIX_KURZ = '290';
|
|
|
|
/** Praefix des langen Nummernkreises (EAN-13). */
|
|
export const PRAEFIX_LANG = '20090';
|
|
|
|
/**
|
|
* Entfernt alles, was keine Ziffer ist. Leere Eingaben werden zu "".
|
|
*
|
|
* @param {unknown} eingabe
|
|
* @returns {string}
|
|
*/
|
|
export function nurZiffern(eingabe) {
|
|
return String(eingabe ?? '').replace(/[^0-9]/g, '');
|
|
}
|
|
|
|
/**
|
|
* Berechnet die GTIN-Pruefziffer fuer eine Ziffernfolge ohne Pruefziffer.
|
|
*
|
|
* Die Ziffern werden von rechts nach links abwechselnd mit 3 und 1 gewichtet
|
|
* (rechteste Ziffer mal 3). Danach gilt (10 - (Summe mod 10)) mod 10.
|
|
*
|
|
* @param {string} ziffern Ziffernfolge ohne Pruefziffer, mindestens eine Ziffer
|
|
* @returns {number} Pruefziffer 0-9
|
|
*/
|
|
export function berechnePruefziffer(ziffern) {
|
|
if (typeof ziffern !== 'string' || !/^[0-9]+$/.test(ziffern)) {
|
|
throw new Error('Prüfziffer kann nur für eine Ziffernfolge berechnet werden.');
|
|
}
|
|
let summe = 0;
|
|
for (let i = 0; i < ziffern.length; i += 1) {
|
|
const ziffer = Number(ziffern[ziffern.length - 1 - i]);
|
|
summe += ziffer * (i % 2 === 0 ? 3 : 1);
|
|
}
|
|
return (10 - (summe % 10)) % 10;
|
|
}
|
|
|
|
/**
|
|
* Prueft eine vollstaendige GTIN als komplette EAN-Nummer.
|
|
*
|
|
* @param {unknown} gtin
|
|
* @returns {boolean} true, wenn Laenge, Ziffern und Pruefziffer stimmen
|
|
*/
|
|
export function gtinGueltig(gtin) {
|
|
const wert = String(gtin ?? '');
|
|
if (!/^(?:[0-9]{8}|[0-9]{13})$/.test(wert)) return false;
|
|
const basis = wert.slice(0, -1);
|
|
return berechnePruefziffer(basis) === Number(wert.slice(-1));
|
|
}
|
|
|
|
/**
|
|
* Bestimmt den Nummernkreis einer normalisierten Artikelnummer.
|
|
*
|
|
* @param {string} ziffern ausschliesslich Ziffern
|
|
* @returns {{ praefix: string, artikelblock: string, typ: GtinTyp, laenge: number } | null}
|
|
*/
|
|
export function bestimmeNummernkreis(ziffern) {
|
|
if (!/^[0-9]+$/.test(ziffern)) return null;
|
|
if (ziffern.length === 3) {
|
|
return {
|
|
praefix: PRAEFIX_KURZ,
|
|
artikelblock: ziffern.padStart(4, '0'),
|
|
typ: 'EAN-8',
|
|
laenge: 8,
|
|
};
|
|
}
|
|
if (ziffern.length === 4) {
|
|
return {
|
|
praefix: PRAEFIX_KURZ,
|
|
artikelblock: ziffern,
|
|
typ: 'EAN-8',
|
|
laenge: 8,
|
|
};
|
|
}
|
|
if (ziffern.length === 7) {
|
|
return {
|
|
praefix: PRAEFIX_LANG,
|
|
artikelblock: ziffern,
|
|
typ: 'EAN-13',
|
|
laenge: 13,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Zustand einer Eingabe fuer die Oberflaeche.
|
|
*
|
|
* @param {unknown} eingabe
|
|
* @returns {'leer' | 'unvollstaendig' | 'ungueltig' | 'ok'}
|
|
*/
|
|
export function eingabeStatus(eingabe) {
|
|
const ziffern = nurZiffern(eingabe);
|
|
if (ziffern.length === 0) return 'leer';
|
|
if (ziffern.length < 3) return 'unvollstaendig';
|
|
return bestimmeNummernkreis(ziffern) ? 'ok' : 'ungueltig';
|
|
}
|
|
|
|
/**
|
|
* Erzeugt aus einer Artikelnummer die vollstaendige GTIN.
|
|
*
|
|
* Die erzeugte GTIN wird anschliessend erneut als komplette EAN validiert
|
|
* (Sicherheitspruefung). Stimmt etwas nicht, wird null zurueckgegeben und es
|
|
* darf kein Barcode gerendert werden.
|
|
*
|
|
* @param {unknown} eingabe Artikelnummer (3, 4 oder 7 Ziffern, Leerzeichen erlaubt)
|
|
* @returns {GtinErgebnis | null}
|
|
*/
|
|
export function erzeugeGtin(eingabe) {
|
|
const ziffern = nurZiffern(eingabe);
|
|
const kreis = bestimmeNummernkreis(ziffern);
|
|
if (!kreis) return null;
|
|
|
|
const basis = kreis.praefix + kreis.artikelblock;
|
|
const pruefziffer = berechnePruefziffer(basis);
|
|
const gtin = `${basis}${pruefziffer}`;
|
|
|
|
if (gtin.length !== kreis.laenge || !gtinGueltig(gtin)) return null;
|
|
|
|
return {
|
|
eingabe: ziffern,
|
|
artikelblock: kreis.artikelblock,
|
|
praefix: kreis.praefix,
|
|
basis,
|
|
pruefziffer: String(pruefziffer),
|
|
gtin,
|
|
typ: kreis.typ,
|
|
laenge: kreis.laenge,
|
|
};
|
|
}
|