/** * 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, }; }