feat: publish unlisted GTIN tool

This commit is contained in:
desfoto automation
2026-09-25 19:01:51 +02:00
parent 68613788b8
commit 160d72f144
35 changed files with 5632 additions and 31 deletions

162
site/gtin/js/gtin.js Normal file
View 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,
};
}