feat: publish unlisted GTIN tool
This commit is contained in:
162
site/gtin/js/gtin.js
Normal file
162
site/gtin/js/gtin.js
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user