= 0; }); });
}
/* Zuweisungen aus dem Vorsorge-Kataster erzeugen */
function schulZuweisen() {
var s = schul(), n = 0;
(S.personen || []).forEach(function (p) {
if (!p.name) return;
var bedarf = personBedarf(p);
s.katalog.forEach(function (k) {
if (!regelPasst(k.regel, bedarf)) return;
var eigene = s.zuw.filter(function (z) { return z.pid === p.id && z.sid === k.id; });
if (eigene.some(function (z) { return !z.erledigt; })) return;
var letzte = eigene.filter(function (z) { return z.erledigt; }).map(function (z) { return z.erledigt; }).sort().pop();
var faellig = letzte ? datumPlus(letzte, 0, +k.intervall || 12) : '';
if (faellig && faellig > datumPlus(heute(), +k.frist || 30)) return;
s.zuw.push({ id: 'zw_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6), pid: p.id, sid: k.id,
zugewiesen: heute(), bis: faellig && faellig > heute() ? faellig : datumPlus(heute(), +k.frist || 30),
erledigt: '', online: false, kursId: '', testBestanden: null, bem: '' });
n++;
});
});
markiereGeaendert();
rendere('schulungen', true);
toast(n ? n + ' Schulungen zugewiesen' : 'Keine neuen Zuweisungen – alles aktuell');
}
function htmlSchulungen() {
var s = schul(), leute = (S.personen || []).filter(function (p) { return p.name; });
var st = { offen: 0, ueberfaellig: 0, erledigt: 0 }, eing = 0;
s.zuw.forEach(function (z) { st[zuwStatus(z)]++; if (z.eingeladen && !z.erledigt) eing++; });
var h = '
Schulungen & Unterweisungen
' +
'
Mitarbeiter erhalten ihre Schulungen und Unterweisungen aus dem Vorsorge-Kataster zugewiesen und absolvieren sie im vorgegebenen Zeitraum. ' +
'Grundlage: § 12 ArbSchG, § 4 DGUV Vorschrift 1 – Unterweisung vor Aufnahme der Tätigkeit und mindestens jährlich.
' +
htmlSchulUebersicht();
var onl = s.katalog.filter(function (k) { return k.url; });
h += '
Online-Schulungen und -Unterweisungen ' + onl.length + ' mit Zugang▾
' +
'
Für Schulungen mit hinterlegtem Zugang erhalten Mitarbeiter eine Einladung per E-Mail mit Link, Zugangsdaten und Frist. ' +
'Nach Abschluss „absolviert“ eintragen – der Nachweis erscheint hier und im Vorsorge-Kataster. Links und Zugangsdaten im Schulungskatalog pflegen.
Zuweisung aus Vorsorge-Kataster: „alle“ oder Stichworte, die in den benötigten Qualifikationen, Vorsorgen oder Beauftragungen ' +
'eines Mitarbeiters vorkommen (z. B. „Stapler“, „G 41“). Intervall in Monaten, Frist = Tage zum Absolvieren nach Zuweisung.
' +
s.katalog.map(function (k, i) {
var b = 'schulungen.katalog.' + i;
return '
' +
feld('Bezeichnung', b + '.titel') + feld('Grundlage', b + '.grundlage') +
feld('Intervall (Monate)', b + '.intervall', 'number', 'step="1"') + feld('Frist zum Absolvieren (Tage)', b + '.frist', 'number', 'step="1"') +
feld('Zuweisung aus Vorsorge-Kataster', b + '.regel') +
feld('Online-Schulung: Link (Zugang)', b + '.url', 'url', 'placeholder="https://…"') +
feld('Weiterer Link (optional)', b + '.url2', 'url', 'placeholder="https://…"') +
feld('Teilnehmerdaten / Zugangsdaten (nur in der Einladung)', b + '.zugang', 'password', 'placeholder="z. B. Benutzer, Passwort oder Kurscode"') +
'
' + (k.url ? '
Ablauf in Schritten (erscheint so in der Einladung)
' +
(k.schritte || []).map(function (x, j) { var sb = b + '.schritte.' + j;
return '
';
}
/* ---------- ISO-9001-Selbstcheck ---------- */
function htmlQmIso() {
var iso = qm().iso, g = qmIsoGrad();
var h = qmKopf('ISO 9001 Selbstcheck', 'Anforderungen der DIN EN ISO 9001:2015 je Kapitel bewerten, Nachweise eintragen und offene Punkte mit Maßnahme, Verantwortlichem und Termin planen.') +
'
' + g.grad + ' % erfüllt · ' + g.offen + ' von ' + g.gesamt + ' Anforderungen offen oder teilweise erfüllt
';
QM_ISO.forEach(function (kap) {
var a = kap[2], ok = a.filter(function (x) { return (iso[x[0]] || {}).st === 'ja' || (iso[x[0]] || {}).st === 'na'; }).length;
h += '
' +
a.map(function (x) {
var e = iso[x[0]] || {}, b = 'qm.iso.' + x[0], st = e.st || '', l = QM_ISO_LINK[x[0]];
return '
' + x[1] + ' ' + esc(x[2]) +
(l ? ' ' : '') + '
' +
'
' + auswahl('Bewertung', b + '.st', QM_ST, 'data-qmneu="1"') + feld('Nachweis / Dokument', b + '.nachweis') +
(st === 'teil' || st === 'nein' ? feld('Maßnahme', b + '.massnahme') + feld('Verantwortlich', b + '.wer') + feld('Termin', b + '.bis', 'date') : '') +
'
';
}).join('') + '
';
});
return h;
}
/* ---------- Dokumentenlenkung ---------- */
function htmlQmDoku() {
var l = qm().doku;
var h = qmKopf('Dokumentenlenkung', 'Dokumentierte Information nach ISO 9001 Abschnitt 7.5: Nummer, Version, Freigabe und nächste Prüfung je Dokument. Dateien werden zusätzlich unter Dokumente › Qualitätsmanagement abgelegt.') +
'
';
l.forEach(function (d, i) {
var b = 'qm.doku.' + i, dat = dokListe('qm').filter(function (x) { return x.erl === d.id; });
h += '
';
}
/* ---------- Interne Audits ---------- */
function htmlQmAudit() {
var l = qm().audits;
var h = qmKopf('Interne Audits', 'Auditprogramm nach ISO 9001 Abschnitt 9.2 und ISO 19011: Audits planen, Feststellungen erfassen, Abweichungen direkt als Korrekturmaßnahme übernehmen.') + '
';
l.forEach(function (a, i) {
var b = 'qm.audits.' + i, f = a.fest || [];
h += '
';
}
/* ---------- Abweichungen & Korrekturmaßnahmen ---------- */
var QMKI = { text: '', bilder: [], laeuft: false, fehler: '', erg: null };
function htmlQmKi() {
var st = QMKI;
var h = '
KI-Assistent: Ursachenanalyse und Korrekturmaßnahmen KI▾
';
if (!kiVerfuegbar()) return h + '
Die KI steht im Online-Kundenportal nach der Anmeldung zur Verfügung.
';
if (!KI_STATUS.wert && !KI_STATUS.laedt) setTimeout(kiStatusLaden, 0);
h += '
Abweichung oder Reklamation beschreiben, optional Fotos. Die KI schlägt Ursachen (5-Why), Sofortmaßnahmen, Korrekturmaßnahmen und die Wirksamkeitsprüfung vor. Vorschlag fachlich prüfen.
' + auswahl('Ergebnis', b + '.wirksam', [['', '– noch offen –'], ['wirksam', 'wirksam'], ['nicht wirksam', 'nicht wirksam']], 'data-qmneu="1"') +
feld('Geprüft am', b + '.wirksamDatum', 'date') + feld('Geprüft von', b + '.wirksamWer') + '
' + bereich('Nachweis der Wirksamkeit', b + '.wirksamText', 2) +
'
';
});
return h + '
';
}
/* ---------- Lieferantenbewertung ---------- */
function htmlQmLief() {
var l = qm().lief;
var h = qmKopf('Lieferantenbewertung', 'Externe Anbieter nach ISO 9001 Abschnitt 8.4 bewerten (1 = ungenügend … 5 = sehr gut). Klasse A ab 4,0 · B ab 3,0 · C unter 3,0 – bei C Maßnahmen oder Lieferantenaudit planen.') + '
';
l.forEach(function (x, i) {
var b = 'qm.lief.' + i, w = liefWert(x), kl = liefKlasse(w);
h += '
';
}
/* ---------- Qualitätsziele & Kennzahlen ---------- */
function htmlQmZiele() {
var l = qm().ziele;
var h = qmKopf('Qualitätsziele & Kennzahlen', 'Messbare Qualitätsziele nach ISO 9001 Abschnitt 6.2 mit Soll/Ist, Verantwortlichem und Termin.') + '
';
l.forEach(function (z, i) {
var b = 'qm.ziele.' + i, e = zielErreicht(z);
h += '
' +
feld('Ziel', b + '.ziel') + feld('Kennzahl', b + '.kennzahl') + feld('Einheit', b + '.einheit') +
auswahl('Bewertung', b + '.richtung', [['max', 'Ist ≥ Soll ist gut'], ['min', 'Ist ≤ Soll ist gut']], 'data-qmneu="1"') +
feld('Soll', b + '.soll', 'text', 'data-qmblur="1"') + feld('Ist', b + '.ist', 'text', 'data-qmblur="1"') + feld('Verantwortlich', b + '.wer') + feld('Termin', b + '.bis', 'date') + '
' +
bereich('Maßnahmen zur Zielerreichung', b + '.massnahmen', 2) + '
';
});
return h + '
';
}
/* ---------- Managementbewertung ---------- */
function htmlQmMgmt() {
var k = qmKennzahlen(), b = 'qm.mgmt';
var z = function (t, v, schlecht) { return '
' + esc(t) + '
' + esc(String(v)) + '
'; };
var h = qmKopf('Managementbewertung', 'Bewertung des QM-Systems durch die oberste Leitung nach ISO 9001 Abschnitt 9.3 – Kennzahlen werden aus allen Bausteinen übernommen.') +
'
' + QM_MGMT_AUS.map(function (x) { return bereich(x[1], b + '.' + x[0], 2); }).join('') +
bereich('Gesamtbewertung: Eignung, Angemessenheit und Wirksamkeit des QM-Systems', b + '.fazit', 3) + '
';
return h;
}
/* ---------- PDF: QM-Bericht ---------- */
function pdfQm() {
var jsPDFctor = (window.jspdf && window.jspdf.jsPDF) || window.jsPDF;
if (!jsPDFctor) { toast('PDF-Bibliothek nicht geladen'); return; }
var q = qm(), k = qmKennzahlen();
var doc = new jsPDFctor({ orientation: 'portrait', unit: 'mm', format: 'a4' });
var B = doc.internal.pageSize.getWidth(), H = doc.internal.pageSize.getHeight(), y = 33, gez = {};
function band() {
var s = doc.internal.getCurrentPageInfo().pageNumber; if (gez[s]) return; gez[s] = true;
doc.setFillColor(34, 31, 31); doc.rect(0, 0, B, 24, 'F'); doc.setFillColor(243, 156, 31); doc.rect(0, 24, B, 1.4, 'F');
doc.setTextColor(243, 156, 31); doc.setFont('helvetica', 'bold'); doc.setFontSize(13); doc.text('ZimmAteC e.K.', 12, 11);
doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.text('Arbeitssicherheit ist kein Luxus', 12, 16.5);
doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text('QM-Bericht ISO 9001', B - 12, 11, { align: 'right' });
doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.text(pdfTxt((gv('kopf.firma') || '') + ' · ' + deDat(heute())), B - 12, 17, { align: 'right' });
doc.setTextColor(0);
}
function ue(t) {
if (y > H - 40) { doc.addPage(); band(); y = 33; }
doc.setFillColor(53, 49, 31); doc.rect(12, y - 5, B - 24, 8, 'F');
doc.setTextColor(243, 156, 31); doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text(pdfTxt(t), 14, y + 0.6);
doc.setTextColor(0); doc.setFont('helvetica', 'normal'); y += 9;
}
function tab(kopf, daten, cs) {
if (!daten.length) daten = [[' – keine Einträge – '].concat(kopf.slice(1).map(function () { return ''; }))];
doc.autoTable({ head: [kopf.map(pdfTxt)], body: daten.map(function (z) { return z.map(function (v) { return pdfTxt(v == null ? '' : String(v)); }); }),
startY: y, margin: { left: 12, right: 12, top: 33, bottom: 16 },
styles: { fontSize: 8, cellPadding: 1.7, overflow: 'linebreak', lineColor: [200, 200, 200], lineWidth: 0.1 },
headStyles: { fillColor: FARBE.bl, textColor: 255, fontStyle: 'bold' }, alternateRowStyles: { fillColor: [245, 243, 239] },
columnStyles: cs || {}, didDrawPage: band });
y = doc.lastAutoTable.finalY + 8;
}
band();
ue('Übersicht');
tab(['Kennzahl', 'Wert'], [['Erfüllungsgrad ISO 9001', k.isoGrad + ' % (' + k.isoOffen + ' offen)'], ['Interne Audits', k.auditsDurch + ' durchgeführt / ' + k.auditsGeplant + ' geplant'],
['Korrekturmaßnahmen', k.capaOffen + ' offen / ' + k.capaGesamt + ' gesamt'], ['Lieferanten', k.liefAnz ? 'Ø ' + String(k.liefSchnitt).replace('.', ',') + ' (' + k.liefC + ' Klasse C)' : '–'],
['Qualitätsziele', k.zieleOk + ' von ' + k.zieleAnz + ' erreicht'], ['Dokumente zur Prüfung fällig', k.dokFaellig]], { 0: { cellWidth: 60, fontStyle: 'bold' } });
ue('ISO 9001 Selbstcheck – offene Anforderungen');
var st = { ja: 'erfüllt', teil: 'teilweise', nein: 'nicht erfüllt', na: 'n. z.', '': 'offen' }, off = [];
QM_ISO.forEach(function (kap) { kap[2].forEach(function (a) { var e = q.iso[a[0]] || {}; if (e.st !== 'ja' && e.st !== 'na') off.push([a[1], a[2], st[e.st || ''], e.massnahme || '', e.wer || '', deDat(e.bis || '')]); }); });
tab(['Kap.', 'Anforderung', 'Status', 'Maßnahme', 'Verantw.', 'Termin'], off, { 0: { cellWidth: 12 }, 2: { cellWidth: 20 }, 5: { cellWidth: 18 } });
ue('Korrekturmaßnahmen');
tab(['Nr.', 'Datum', 'Quelle', 'Abweichung', 'Ursache', 'Status'], q.capa.map(function (c) { return [c.nr, deDat(c.datum), c.quelle, (c.titel ? c.titel + ': ' : '') + (c.beschreibung || ''), c.ursache, capaStatus(c)]; }),
{ 0: { cellWidth: 20 }, 1: { cellWidth: 18 }, 5: { cellWidth: 22 } });
ue('Interne Audits');
tab(['Datum', 'Art', 'Prozess', 'Kapitel', 'Status', 'Feststellungen'], q.audits.map(function (a) {
return [deDat(a.datum), a.art || 'Internes Audit', a.prozess, a.kapitel, a.status || 'geplant', (a.fest || []).map(function (f) { return (f.art || '') + ': ' + (f.text || ''); }).join('\n')]; }),
{ 0: { cellWidth: 18 }, 4: { cellWidth: 20 } });
ue('Lieferantenbewertung');
tab(['Lieferant', 'Leistung'].concat(QM_KRIT.map(function (x) { return x[1]; })).concat(['Ø', 'Kl.']), q.lief.map(function (l) {
var w = liefWert(l); return [l.name, l.leistung].concat(QM_KRIT.map(function (x) { return (l.bew || {})[x[0]] || ''; })).concat([w === null ? '' : String(w).replace('.', ','), liefKlasse(w)]); }));
ue('Qualitätsziele');
tab(['Ziel', 'Kennzahl', 'Soll', 'Ist', 'Verantw.', 'Ergebnis'], q.ziele.map(function (z) { var e = zielErreicht(z);
return [z.ziel, z.kennzahl, (z.soll || '') + ' ' + (z.einheit || ''), (z.ist || '') + ' ' + (z.einheit || ''), z.wer, e === null ? '' : e ? 'erreicht' : 'nicht erreicht']; }));
ue('Gelenkte Dokumente');
tab(['Nr.', 'Titel', 'Art', 'Rev.', 'Freigabe', 'Nächste Prüfung'], q.doku.map(function (d) { return [d.nr, d.titel, d.art, d.version, (d.freigeber || '') + (d.freigabe ? ' ' + deDat(d.freigabe) : ''), deDat(d.naechste || '')]; }));
if (q.mgmt.datum || q.mgmt.fazit) {
ue('Managementbewertung ' + deDat(q.mgmt.datum || ''));
tab(['Thema', 'Bewertung'], QM_MGMT_EIN.concat(QM_MGMT_AUS).concat([['fazit', 'Gesamtbewertung']]).filter(function (x) { return q.mgmt[x[0]]; })
.map(function (x) { return [x[1], q.mgmt[x[0]]]; }), { 0: { cellWidth: 60, fontStyle: 'bold' } });
}
var n = doc.internal.getNumberOfPages(), i;
for (i = 1; i <= n; i++) {
doc.setPage(i); doc.setDrawColor(200); doc.setLineWidth(0.2); doc.line(12, H - 12, B - 12, H - 12);
doc.setFontSize(7.5); doc.setTextColor(110); doc.text(pdfTxt('ZimmAteC e.K. · Waghäusel · QM-Bericht nach DIN EN ISO 9001:2015'), 12, H - 8);
doc.text('Seite ' + i + ' von ' + n, B - 12, H - 8, { align: 'right' });
}
var name = 'QM-Bericht_' + dateiBasis() + '.pdf';
doc.save(name);
try { dokAblegen(doc.output('blob'), name, 'qm', 'QM-Bericht ' + deDat(heute()), 'QM-Bericht erstellt'); } catch (e) { toast('QM-Bericht erstellt'); }
}
/* ---------- Demo ---------- */
function qmDemo() {
var q = qm(), d = heute();
['k4_1', 'k4_3', 'k5_2', 'k5_3', 'k7_1', 'k7_2', 'k8_2', 'k8_5', 'k8_6'].forEach(function (k) { q.iso[k] = { st: 'ja', nachweis: 'QM-Handbuch Kap. ' + k.slice(1).replace('_', '.') }; });
q.iso.k6_1 = { st: 'teil', nachweis: 'Risikoliste unvollständig', massnahme: 'Risiken und Chancen je Prozess ergänzen', wer: 'QMB', bis: datumPlus(d, 30) };
q.iso.k9_12 = { st: 'nein', massnahme: 'Kundenbefragung einführen (jährlich)', wer: 'Vertrieb', bis: datumPlus(d, 60) };
q.doku.push({ id: qmId('dk'), nr: 'QMH-01', titel: 'QM-Handbuch', art: 'QM-Handbuch', version: '3', ersteller: 'QMB', freigeber: 'Geschäftsführung', freigabe: datumPlus(d, 0, -13), naechste: datumPlus(d, 0, -1) },
{ id: qmId('dk'), nr: 'VA-7.5', titel: 'Lenkung dokumentierter Information', art: 'Verfahrensanweisung', version: '2', freigeber: 'Geschäftsführung', freigabe: datumPlus(d, 0, -3), naechste: datumPlus(d, 0, 9) });
var c = capaAnlegen({ titel: 'Falsch gebohrte Anschlussbleche', quelle: 'Reklamation Kunde', bereich: 'Fertigung / Bohren', beschreibung: 'Kunde reklamiert 12 Anschlussbleche mit falschem Lochabstand.',
sofort: 'Lagerbestand gesperrt und geprüft, Ersatzteile gefertigt.', ursache: 'Zeichnung mit altem Revisionsstand an der Maschine – Zeichnungsausgabe nicht gelenkt.',
massnahmen: [{ text: 'Zeichnungsausgabe nur über aktuelle Revision (Stempel „gültig“)', wer: 'AV', bis: datumPlus(d, 14), erl: '' }, { text: 'Unterweisung Fertigung zu Revisionsständen', wer: 'Meister', bis: datumPlus(d, -3), erl: '' }] });
q.audits.push({ id: qmId('au'), datum: datumPlus(d, 0, -1), art: 'Internes Audit', prozess: 'Fertigung', kapitel: '7.5, 8.5', auditor: 'QMB', status: 'durchgeführt', fazit: 'Prozess weitgehend beherrscht.',
fest: [{ art: 'Abweichung', kap: '7.5', text: 'Zeichnungen mit veraltetem Revisionsstand an der Maschine.', capa: c.id }, { art: 'Positiv', kap: '8.5', text: 'Rückverfolgbarkeit über Chargennummern vorbildlich.' }] },
{ id: qmId('au'), datum: datumPlus(d, 45), art: 'Internes Audit', prozess: 'Einkauf', kapitel: '8.4', auditor: 'QMB', status: 'geplant', fest: [] });
q.lief.push({ id: qmId('lf'), name: 'Stahlhandel Muster GmbH', leistung: 'Bleche, Profile', datum: d, bew: { q: '4', t: '3', p: '4', k: '5', a: '4' }, zert: 'ISO 9001' },
{ id: qmId('lf'), name: 'Beispiel Verzinkerei', leistung: 'Feuerverzinken', datum: d, bew: { q: '2', t: '3', p: '3', k: '2', a: '3' }, zert: 'keine', bem: 'Lieferantengespräch führen, Audit planen.' });
q.ziele.push({ id: qmId('zl'), ziel: 'Reklamationsquote senken', kennzahl: 'Reklamationen je 100 Aufträge', einheit: '%', richtung: 'min', soll: '1,5', ist: '2,1', wer: 'Fertigungsleitung', bis: datumPlus(d, 0, 3) },
{ id: qmId('zl'), ziel: 'Termintreue erhöhen', kennzahl: 'pünktlich gelieferte Aufträge', einheit: '%', richtung: 'max', soll: '95', ist: '96', wer: 'AV', bis: datumPlus(d, 0, 6) });
qmNeu(); toast('Beispieldaten angelegt');
}
/* ---------- Ereignisse ---------- */
document.addEventListener('click', function (e) {
var t = e.target, x;
if (!t.closest) return;
if (t.closest('[data-qmdemo]')) { qmDemo(); return; }
if (t.closest('[data-qmpdf]')) { pdfQm(); return; }
if ((x = t.closest('[data-qmgo]'))) { zeigeTab(x.getAttribute('data-qmgo')); return; }
if ((x = t.closest('[data-qmadd]'))) {
var a = x.getAttribute('data-qmadd'), q = qm();
if (a === 'capa') capaAnlegen({});
else if (a === 'doku') q.doku.push({ id: qmId('dk'), nr: 'QM-' + String(q.doku.length + 1).padStart(2, '0'), titel: '', art: '', version: '1' });
else if (a === 'audits') q.audits.push({ id: qmId('au'), datum: '', art: 'Internes Audit', status: 'geplant', fest: [] });
else if (a === 'lief') q.lief.push({ id: qmId('lf'), name: '', datum: heute(), bew: {} });
else if (a === 'ziele') q.ziele.push({ id: qmId('zl'), ziel: '', richtung: 'max' });
qmNeu();
var r = document.querySelectorAll('#p-' + aktiv + ' .row-card'); if (r.length) { r[r.length - 1].classList.remove('closed'); r[r.length - 1].scrollIntoView({ block: 'center' }); }
return;
}
if ((x = t.closest('[data-qmdel]'))) {
var p = x.getAttribute('data-qmdel').split('.'), idx = +p.pop(), arr = gv('qm.' + p.join('.'));
if (Array.isArray(arr) && confirm('Eintrag entfernen?')) { arr.splice(idx, 1); qmNeu(); }
return;
}
if ((x = t.closest('[data-qmfest]'))) { var au = qm().audits[+x.getAttribute('data-qmfest')]; (au.fest = au.fest || []).push({ art: 'Abweichung', kap: '', text: '' }); qmNeu(); return; }
if ((x = t.closest('[data-qmmn]'))) { var ca = qm().capa[+x.getAttribute('data-qmmn')]; (ca.massnahmen = ca.massnahmen || []).push({ text: '', wer: '', bis: datumPlus(heute(), 30), erl: '' }); qmNeu(); return; }
if ((x = t.closest('[data-qmzucapa]'))) {
var ij = x.getAttribute('data-qmzucapa').split('.'), a2 = qm().audits[+ij[0]], f = a2.fest[+ij[1]];
var c = capaAnlegen({ titel: 'Audit-Abweichung ' + (f.kap ? 'Kap. ' + f.kap : '') + (a2.prozess ? ' – ' + a2.prozess : ''), quelle: 'Internes Audit', bereich: a2.prozess || '', beschreibung: f.text || '' });
f.capa = c.id; markiereGeaendert(); toast('Korrekturmaßnahme ' + c.nr + ' angelegt'); qmNeu(); return;
}
if (t.closest('[data-qmkilos]')) { qmKiLos(); return; }
if ((x = t.closest('[data-qmkifdel]'))) { QMKI.bilder.splice(+x.getAttribute('data-qmkifdel'), 1); qmKiNeu(); return; }
if (t.closest('[data-qmkiverw]')) { QMKI.erg = null; qmKiNeu(); return; }
if (t.closest('[data-qmkiueb]')) {
var er = QMKI.erg;
capaAnlegen({ titel: String(QMKI.text).split(/[.\n]/)[0].slice(0, 80), quelle: er.quelle && QM_QUELLE.indexOf(er.quelle) >= 0 ? er.quelle : '', beschreibung: QMKI.text,
sofort: (er.sofort || []).join('\n'), ursache: (er.warum || []).map(function (w, i) { return (i + 1) + '. Warum? ' + w; }).join('\n') + (er.grundursache ? '\nGrundursache: ' + er.grundursache : ''),
massnahmen: (er.massnahmen || []).map(function (m) { return { text: m.text, wer: m.rolle || '', bis: datumPlus(heute(), +m.frist_tage || 30), erl: '' }; }),
wirksamText: er.wirksamkeit ? 'Geplant: ' + er.wirksamkeit : '', ki: true });
QMKI.erg = null; QMKI.text = ''; QMKI.bilder = [];
qmNeu(); toast('Korrekturmaßnahme aus KI-Vorschlag angelegt'); return;
}
});
document.addEventListener('input', function (e) {
var el = e.target;
if (el && el.hasAttribute && el.hasAttribute('data-qmkitext')) {
QMKI.text = el.value;
var b = document.querySelector('[data-qmkilos]'); if (b && !QMKI.laeuft) b.disabled = !QMKI.bilder.length && QMKI.text.trim().length < 3;
}
});
document.addEventListener('change', function (e) {
var el = e.target;
if (!el || !el.hasAttribute) return;
if (el.hasAttribute('data-qmneu') || el.hasAttribute('data-qmblur')) {
if (el.hasAttribute('data-k')) sv(el.getAttribute('data-k'), el.value);
var y = window.scrollY; setTimeout(function () { rendere(aktiv, true); window.scrollTo(0, y); }, 0);
return;
}
if (el.hasAttribute('data-qmkifoto')) {
Promise.all(Array.prototype.slice.call(el.files || []).slice(0, 3 - QMKI.bilder.length).map(fotoVerkleinern)).then(function (l) {
QMKI.bilder = QMKI.bilder.concat(l.filter(Boolean)).slice(0, 3); qmKiNeu();
});
el.value = ''; return;
}
if (el.hasAttribute('data-qmdatei')) {
var d = qm().doku[+el.getAttribute('data-qmdatei')];
Array.prototype.slice.call(el.files || []).forEach(function (f) {
if (f.size > 25 * 1048576) { toast(f.name + ': größer als 25 MB'); return; }
dokAblegen(f, f.name, 'qm', 'QM-Dokument ' + (d.nr || '') + ' ' + (d.titel || ''), 'Datei abgelegt', d.id);
});
el.value = '';
}
});
var QM_HTML = { qm_iso: htmlQmIso, qm_doku: htmlQmDoku, qm_audit: htmlQmAudit, qm_capa: htmlQmCapa, qm_lief: htmlQmLief, qm_ziele: htmlQmZiele, qm_mgmt: htmlQmMgmt };
if (typeof DOK_PANELS !== 'undefined') DOK_PANELS.push('qm_doku');
/* Bestätigungsdialoge: In Vorschauen/eingebetteten Ansichten blockiert der Browser confirm() und liefert sofort „Abbrechen“ – dann gilt der Klick als bestätigt. */
(function(){if(window.__cfOk)return;window.__cfOk=1;var c=window.confirm&&window.confirm.bind(window);if(!c)return;window.confirm=function(m){var t=Date.now(),r=c(m);return r||(Date.now()-t<15);};})();
/* ---------------------------------------------------------------
Niederlassungen: je Niederlassung ein eigener Datenbestand
(Unternehmensdaten, Begehung, GBU, Kataster, Prüfungen, Brandschutz,
QM …). Aktive Niederlassung liegt in S, die übrigen in S.nl.daten.
Kataster und Prüfungen (eigene Tabellen) tragen die Kennung nl und
werden beim Laden auf die Niederlassungen verteilt. Ohne Kennung =
erste Niederlassung (n1) – bestehende Daten bleiben unverändert.
--------------------------------------------------------------- */
var NL_GLOBAL = ['nl', 'prueferEigene', 'prueferAus', 'prueferSig', 'kundeNamen'];
var NL_LISTEN = ['kataster', 'arbeitsmittel'];
function nl() {
if (!S.nl || typeof S.nl !== 'object') S.nl = {};
var n = S.nl;
if (!Array.isArray(n.liste) || !n.liste.length) n.liste = [{ id: 'n1', name: gv('kopf.niederlassung') || 'Hauptsitz' }];
if (!n.daten || typeof n.daten !== 'object') n.daten = {};
if (!n.liste.some(function (x) { return x.id === n.akt; })) n.akt = n.liste[0].id;
return n;
}
function nlName(id) { var o = nl().liste.filter(function (x) { return x.id === id; })[0]; return o ? o.name : ''; }
function nlLeer(name) {
var z = leererZustand();
z.kopf.firma = gv('kopf.firma'); z.kopf.branche = gv('kopf.branche'); z.kopf.niederlassung = name || '';
NL_GLOBAL.forEach(function (k) { delete z[k]; });
return z;
}
function nlSnap() {
var o = {};
Object.keys(S).forEach(function (k) { if (NL_GLOBAL.indexOf(k) < 0) o[k] = S[k]; });
return JSON.parse(JSON.stringify(o));
}
/* Kataster/Prüfungen ohne Kennung gehören zu n1; in anderen Niederlassungen erfasste bekommen die Kennung */
function nlMarkieren() {
var a = nl().akt;
if (a === 'n1') return;
NL_LISTEN.forEach(function (k) { (S[k] || []).forEach(function (e) { if (!e.nl) e.nl = a; }); });
}
function nlVerteilen() {
var n = nl();
if (n.liste.length < 2) return;
var ids = n.liste.map(function (x) { return x.id; });
NL_LISTEN.forEach(function (k) {
var bleib = [];
(S[k] || []).forEach(function (e) {
var o = e.nl && ids.indexOf(e.nl) >= 0 ? e.nl : 'n1';
if (ids.indexOf(o) < 0) o = ids[0];
if (o === n.akt) { bleib.push(e); return; }
var st = n.daten[o] || (n.daten[o] = nlLeer(nlName(o)));
st[k] = (st[k] || []).filter(function (x) { return x.id !== e.id; });
st[k].push(e);
});
S[k] = bleib;
});
}
var nlWechselLaeuft = false;
async function nlWechseln(id) {
var n = nl();
if (id === n.akt || nlWechselLaeuft) return;
nlWechselLaeuft = true;
try {
nlMarkieren();
if (window.__PORTAL_ON && PORTAL.pruefSync) { try { await pmSpeichern(); } catch (e) {} }
var firma = gv('kopf.firma'), branche = gv('kopf.branche');
n.daten[n.akt] = nlSnap();
var neu = n.daten[id] || nlLeer(nlName(id));
delete n.daten[id];
Object.keys(S).forEach(function (k) { if (NL_GLOBAL.indexOf(k) < 0) delete S[k]; });
Object.keys(neu).forEach(function (k) { if (NL_GLOBAL.indexOf(k) < 0) S[k] = neu[k]; });
if (!S.kopf) S.kopf = {};
S.kopf.firma = firma; if (!S.kopf.branche) S.kopf.branche = branche;
if (!S.kopf.niederlassung) S.kopf.niederlassung = nlName(id);
n.akt = id;
strukturErgaenzen();
markiereGeaendert();
try { kopfzeileAktualisieren(); } catch (e) {}
gerendert.home = false;
alleNeuRendern();
subnavZeichnen();
toast('Niederlassung „' + nlName(id) + '“ – Daten gefiltert');
} finally { nlWechselLaeuft = false; }
}
function nlHinzufuegen(name) {
var n = nl(); name = String(name || '').trim() || 'Niederlassung ' + (n.liste.length + 1);
var id = 'n' + Date.now().toString(36);
n.liste.push({ id: id, name: name });
n.daten[id] = nlLeer(name);
markiereGeaendert();
return nlWechseln(id);
}
function nlEntfernen(id) {
var n = nl();
if (n.liste.length < 2 || id === 'n1') return;
if (!confirm('Niederlassung „' + nlName(id) + '“ mit allen Daten entfernen? Kataster- und Prüfeinträge dieser Niederlassung werden der Hauptniederlassung zugeordnet.')) return;
var weiter = function () {
var st = n.daten[id] || {};
var ziel = n.daten.n1 || (n.akt === 'n1' ? S : null);
NL_LISTEN.forEach(function (k) {
(st[k] || []).forEach(function (e) { delete e.nl; if (ziel) (ziel[k] = ziel[k] || []).push(e); });
});
delete n.daten[id];
n.liste = n.liste.filter(function (x) { return x.id !== id; });
markiereGeaendert(); alleNeuRendern(); subnavZeichnen();
toast('Niederlassung entfernt');
};
if (n.akt === id) nlWechseln('n1').then(weiter); else weiter();
}
/* ---------- Oberfläche ---------- */
function htmlNlWahl(klasse) {
var n = nl();
return '';
}
function nlKopfZeichnen() {
var hd = document.querySelector('header .hd');
if (!hd) return;
var el = document.getElementById('nlKopf');
if (!el) {
el = document.createElement('div'); el.id = 'nlKopf'; el.className = 'nl-kopf';
var chip = document.getElementById('saveChip');
hd.insertBefore(el, chip || null);
}
el.innerHTML = 'Niederlassung' + htmlNlWahl();
}
function htmlNlKarte() {
var n = nl();
return '
Niederlassungen ' + n.liste.length + '▾
' +
'
Jede Niederlassung hat eigene Daten: Unternehmensdaten, Kennzahlen, Begehungen, Gefährdungsbeurteilungen, Kataster, Prüfungen, Brandschutz und QM. ' +
'Die Auswahl oben rechts filtert alle Bereiche auf die gewählte Niederlassung.
';
}
function mitNl(h) {
/* Niederlassungen direkt nach den Angaben zum Unternehmen */
var k = htmlNlKarte(), a = h.indexOf('
Angaben zum Unternehmen'), j = a >= 0 ? h.indexOf('
= 0;
});
var bv = (S.bauKopf && S.bauKopf.bauvorhaben) || '';
var h = '
BG BAU Bausteine
Bausteine der BG BAU für diese Baustelle' + (bv ? ' (' + esc(bv) + ')' : '') +
' auswählen, als Unterweisungsgrundlage nutzen und die Unterweisung mit Teilnehmern dokumentieren. „öffnen“ führt zum Baustein im Medien-Center der BG BAU.
' +
'
' + b.wahl.length + '
Bausteine ausgewählt
' + b.unterweisungen.length + '
Unterweisungen dokumentiert
' + htmlKiAssist('bgbausteine');
h += '
Pakete nach Gewerk ▾
Vorschläge – fügt die Bausteine zur Auswahl hinzu.
';
return h;
}
function bgbPdf(u) {
var jsPDFctor = (window.jspdf && window.jspdf.jsPDF) || window.jsPDF;
if (!jsPDFctor) { toast('PDF-Bibliothek nicht geladen'); return; }
var doc = new jsPDFctor({ unit: 'mm', format: 'a4' }), B = doc.internal.pageSize.getWidth(), H = doc.internal.pageSize.getHeight(), gez = {};
function band() {
var s = doc.internal.getCurrentPageInfo().pageNumber; if (gez[s]) return; gez[s] = true;
doc.setFillColor(34, 31, 31); doc.rect(0, 0, B, 24, 'F'); doc.setFillColor(243, 156, 31); doc.rect(0, 24, B, 1.4, 'F');
doc.setTextColor(243, 156, 31); doc.setFont('helvetica', 'bold'); doc.setFontSize(13); doc.text('ZimmAteC e.K.', 12, 11);
doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'normal'); doc.setFontSize(9); doc.text('Arbeitssicherheit ist kein Luxus', 12, 16.5);
doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text('Unterweisungsnachweis', B - 12, 11, { align: 'right' });
doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.text(pdfTxt('§ 12 ArbSchG · § 4 DGUV Vorschrift 1'), B - 12, 17, { align: 'right' }); doc.setTextColor(0);
}
var tab = function (kopf, daten, y, cs) {
doc.autoTable({ head: [kopf.map(pdfTxt)], body: daten.map(function (z) { return z.map(function (v) { return pdfTxt(String(v || '')); }); }), startY: y,
margin: { left: 12, right: 12, top: 33, bottom: 16 }, styles: { fontSize: 8.5, cellPadding: 2, lineColor: [200, 200, 200], lineWidth: 0.1 },
headStyles: { fillColor: FARBE.bl, textColor: 255, fontStyle: 'bold' }, columnStyles: cs || {}, didDrawPage: band });
return doc.lastAutoTable.finalY + 8;
};
band();
var y = tab(['Angabe', 'Eintrag'], [['Firma', gv('kopf.firma')], ['Baustelle / Ort', u.ort || (S.bauKopf && S.bauKopf.bauvorhaben) || ''], ['Datum', deDat(u.datum)], ['Unterweisende Person', u.wer]], 33, { 0: { cellWidth: 50, fontStyle: 'bold' } });
y = tab(['Baustein', 'Thema (BG BAU)'], u.bausteine.map(function (c) { return [c, bgbTitel(c)]; }), y, { 0: { cellWidth: 22 } });
var tn = (u.teilnehmer || []).length ? u.teilnehmer : ['', '', '', '', '', ''];
y = tab(['Nr.', 'Name, Vorname / Firma', 'Unterschrift'], tn.map(function (n, i) { return [String(i + 1), n, '']; }), y, { 0: { cellWidth: 12 }, 2: { cellWidth: 60, minCellHeight: 10 } });
if (y > H - 30) { doc.addPage(); band(); y = 40; }
doc.setFontSize(9); doc.line(12, y + 12, 90, y + 12); doc.text(pdfTxt('Datum, Unterschrift unterweisende Person'), 12, y + 16);
var n = doc.internal.getNumberOfPages(), i;
for (i = 1; i <= n; i++) { doc.setPage(i); doc.setFontSize(7.5); doc.setTextColor(110); doc.text(pdfTxt('Inhalte: Bausteine der BG BAU (www.bgbau.de)'), 12, H - 8); doc.text('Seite ' + i + ' von ' + n, B - 12, H - 8, { align: 'right' }); }
var name = 'Unterweisung_BG-Bausteine_' + String(gv('kopf.firma') || 'Baustelle').replace(/[^\wäöüÄÖÜß-]+/g, '_') + '_' + (u.datum || heute()) + '.pdf';
doc.save(name);
try { dokAblegen(doc.output('blob'), name, 'sonst', 'Unterweisung Baustelle ' + deDat(u.datum), 'Unterweisungsnachweis erstellt'); } catch (e) { toast('Unterweisungsnachweis erstellt'); }
}
document.addEventListener('change', function (e) {
var el = e.target;
if (!el || !el.hasAttribute) return;
var b;
if (el.hasAttribute('data-bgbwahl')) {
b = bgb(); var c = el.getAttribute('data-bgbwahl'), i = b.wahl.indexOf(c);
if (el.checked && i < 0) b.wahl.push(c); if (!el.checked && i >= 0) b.wahl.splice(i, 1);
b.wahl.sort(); markiereGeaendert(); el.closest('.bgb-z').classList.toggle('an', el.checked);
var k = document.querySelector('#p-bgbausteine .kpi .v'); if (k) k.textContent = b.wahl.length;
return;
}
if (el.hasAttribute('data-bgbg')) { BGB_FILTER.g = el.value; rendere('bgbausteine', true); return; }
if (el.hasAttribute('data-bgbnur')) { BGB_FILTER.nurWahl = el.checked; rendere('bgbausteine', true); }
});
var bgbT = null;
document.addEventListener('input', function (e) {
var el = e.target;
if (!el || !el.hasAttribute || !el.hasAttribute('data-bgbq')) return;
BGB_FILTER.q = el.value; clearTimeout(bgbT);
bgbT = setTimeout(function () { rendere('bgbausteine', true); var n = document.querySelector('#p-bgbausteine [data-bgbq]'); if (n) { n.focus(); n.setSelectionRange(n.value.length, n.value.length); } }, 250);
});
document.addEventListener('click', function (e) {
var t = e.target, x, b;
if (!t.closest) return;
if ((x = t.closest('[data-bgbpaket]'))) {
b = bgb(); BGB_PAKETE[+x.getAttribute('data-bgbpaket')][1].forEach(function (c) { if (b.wahl.indexOf(c) < 0) b.wahl.push(c); });
b.wahl.sort(); markiereGeaendert(); rendere('bgbausteine', true); toast('Bausteine hinzugefügt'); return;
}
if (t.closest('[data-bgbleer]')) { if (confirm('Auswahl leeren?')) { bgb().wahl = []; markiereGeaendert(); rendere('bgbausteine', true); } return; }
if (t.closest('[data-bgbunt]')) {
b = bgb(); var n = b.neu || {};
var u = { datum: n.datum || heute(), wer: n.wer || '', ort: n.ort || '', bausteine: b.wahl.slice(),
teilnehmer: String(n.teilnehmer || '').split('\n').map(function (s) { return s.trim(); }).filter(Boolean) };
b.unterweisungen.unshift(u); b.neu = {}; markiereGeaendert(); rendere('bgbausteine', true); bgbPdf(u); return;
}
if ((x = t.closest('[data-bgbpdf]'))) { bgbPdf(bgb().unterweisungen[+x.getAttribute('data-bgbpdf')]); return; }
if ((x = t.closest('[data-bgbudel]'))) { if (confirm('Unterweisung entfernen?')) { bgb().unterweisungen.splice(+x.getAttribute('data-bgbudel'), 1); markiereGeaendert(); rendere('bgbausteine', true); } }
});
/* ---------------------------------------------------------------
Baustelle: Begehung enge Räume (Enge-Räume-App, gleiche Daten wie
Erlaubnisverfahren) und Arbeiten mit Absturzgefahr (TRBS 2121,
ASR A2.1, DGUV Regel 112-198/199) mit PDF.
--------------------------------------------------------------- */
var ENGE_BAU = false;
function htmlBauEngeRaeume() {
/* nur ein iframe der Enge-Räume-App gleichzeitig – sonst überschreiben sich die Stände */
var pe = document.getElementById('p-erlaubnis'); if (pe && erlOffen) { pe.innerHTML = ''; gerendert.erlaubnis = false; }
erlOffen = 'engeraeume'; ENGE_BAU = true;
if (!KI_STATUS.wert && !KI_STATUS.laedt && kiVerfuegbar()) setTimeout(kiStatusLaden, 0);
var bv = (S.bauKopf && S.bauKopf.bauvorhaben) || '';
return '
Begehung enge Räume KI
' +
'
Schächte, Behälter, Kanäle, Gruben und enge Räume auf der Baustelle' + (bv ? ' (' + esc(bv) + ')' : '') +
' nach DGUV Regel 113-004: Gefährdungsbeurteilung, Freimessen, Befahrerlaubnis und Bericht – mit KI-Assistent. Gleiche Daten wie Arbeitsschutz › Erlaubnisverfahren.
' +
'';
}
/* ---------- Arbeiten mit Absturzgefahr ---------- */
var AB_ARTEN = ['Dachrand / Attika', 'Dachfläche (Lichtkuppel, Lichtband, nicht durchtrittsicher)', 'Deckenrand / Geschossdecke', 'Boden- oder Deckenöffnung', 'Wandöffnung / Fenster',
'Treppe / Treppenauge', 'Gerüst (Auf-, Um-, Abbau)', 'Gerüst (Nutzung)', 'Leiter', 'Hubarbeitsbühne', 'Baugrube / Graben / Schacht', 'Stahl- / Holzbau-Montage', 'Arbeiten über Wasser oder Schüttgut', 'Sonstiges'];
/* Rangfolge nach TRBS 2121 Teil 1: technisch-kollektiv vor individuell */
var AB_MASSN = [
['kollektiv', '1 · Absturzsicherung (Seitenschutz, Geländer, Abdeckung, Absperrung)'],
['geruest', '1 · Gerüst / Arbeitsbühne / Hubarbeitsbühne'],
['auffang', '2 · Auffangeinrichtung (Schutznetz, Fanggerüst, Dachfanggerüst)'],
['psaga', '3 · PSA gegen Absturz (nur wenn 1 und 2 nicht möglich)'],
['leiter', 'Leiter als Arbeitsplatz (nur geringe Gefährdung, kurze Dauer)']
];
var AB_CHECK = [
['Gefährdungsbeurteilung für Arbeiten mit Absturzgefahr liegt vor und ist aktuell', '§ 5 ArbSchG, § 3 BetrSichV'],
['Absturzkanten ab mehr als 1 m Absturzhöhe gesichert; bei Gefahr des Versinkens unabhängig von der Höhe', 'ASR A2.1'],
['Rangfolge beachtet: kollektive Absturzsicherung vor Auffangeinrichtung vor PSA gegen Absturz', 'TRBS 2121 Teil 1'],
['Seitenschutz vollständig: Geländerholm, Zwischenholm, Bordbrett, ausreichend befestigt', 'DIN EN 13374'],
['Öffnungen in Böden und Decken durchtrittsicher, unverschiebbar abgedeckt und gekennzeichnet', 'ASR A2.1'],
['Nicht durchtrittsichere Bauteile (Lichtkuppeln, Lichtbänder, Wellplatten) gesichert oder abgesperrt', 'ASR A2.1, DGUV I 201-056'],
['Gerüste von befähigter Person geprüft, gekennzeichnet (Freigabe), Plan für den Gebrauch vorhanden', 'TRBS 2121 Teil 1, DIN EN 12811'],
['Hubarbeitsbühnen: Bediener beauftragt und unterwiesen, Rettungskonzept bei Ausfall vorhanden', 'DGUV G 308-008'],
['Leitern nur bei geringer Gefährdung und kurzer Dauer als Arbeitsplatz genutzt; Zustand geprüft', 'TRBS 2121 Teil 2'],
['PSA gegen Absturz: geeignete Anschlageinrichtung (Tragfähigkeit) festgelegt', 'DGUV R 112-198, DIN EN 795'],
['PSA gegen Absturz jährlich durch Sachkundigen geprüft, Sichtprüfung vor Benutzung', 'DGUV G 312-906'],
['Unterweisung PSA gegen Absturz mit praktischen Übungen mindestens jährlich', '§ 31 DGUV V 1, DGUV R 112-198'],
['Rettungskonzept für hängende Personen (Hängetrauma) festgelegt, Rettungsausrüstung vor Ort', 'DGUV R 112-199'],
['Arbeitsmedizinische Vorsorge Arbeiten mit Absturzgefahr angeboten bzw. berücksichtigt', 'AMR 3.3 / DGUV Empfehlung'],
['Witterung berücksichtigt (Wind, Nässe, Eis) – Arbeiten bei Gefahr eingestellt', 'TRBS 2121 Teil 1'],
['Bereiche unterhalb gegen herabfallende Gegenstände gesichert (Schutzdach, Absperrung)', 'ASR A2.1']
];
var AB_BAUSTEINE = ['B 100', 'B 102', 'B 105', 'B 107', 'B 111', 'B 112', 'B 113', 'B 121', 'B 131', 'B 132', 'B 212', 'C 345', 'C 346', 'C 359', 'C 360', 'E 601'];
function ab() {
if (!S.absturz || typeof S.absturz !== 'object') S.absturz = {};
var a = S.absturz;
if (!a.kopf) a.kopf = { datum: heute(), ersteller: '', ort: '' };
if (!Array.isArray(a.plaetze)) a.plaetze = [];
if (!a.check || typeof a.check !== 'object') a.check = {};
return a;
}
function abHinweise(p) {
var h = [], hoe = parseFloat(String(p.hoehe || '').replace(',', '.'));
if (!p.massn) h.push(['hoch', 'Schutzmaßnahme festlegen']);
if (p.massn === 'psaga') {
if (!String(p.begruendung || '').trim()) h.push(['hoch', 'Begründung erforderlich, warum kollektive Maßnahmen oder Auffangeinrichtungen nicht möglich sind (TRBS 2121)']);
if (!p.anschlag) h.push(['hoch', 'Anschlageinrichtung festlegen (DIN EN 795)']);
if (!p.rettung) h.push(['hoch', 'Rettungskonzept für hängende Personen fehlt (DGUV R 112-199)']);
if (!p.unterwiesen) h.push(['mittel', 'Unterweisung PSAgA mit praktischen Übungen nachweisen']);
}
if (p.massn === 'leiter' && !isNaN(hoe) && hoe > 5) h.push(['hoch', 'Leiter als Arbeitsplatz bei über 5 m Standhöhe nicht zulässig – Gerüst oder Hubarbeitsbühne einsetzen (TRBS 2121 Teil 2)']);
if (p.massn === 'leiter' && p.dauer === 'lang') h.push(['hoch', 'Leiter nur für kurzzeitige Arbeiten mit geringer Gefährdung – anderes Arbeitsmittel wählen (TRBS 2121 Teil 2)']);
if (p.massn === 'geruest' && !p.freigabe) h.push(['mittel', 'Gerüstfreigabe / Prüfung durch befähigte Person dokumentieren']);
if (!isNaN(hoe) && hoe <= 1 && !/Wasser/.test(p.art || '')) h.push(['gering', 'Bis 1 m Absturzhöhe in der Regel keine Absturzsicherung nach ASR A2.1 erforderlich – Gefährdungsbeurteilung beachten']);
return h;
}
function htmlAbsturz() {
var a = ab(), bv = (S.bauKopf && S.bauKopf.bauvorhaben) || '';
var offen = a.plaetze.filter(function (p) { return abHinweise(p).some(function (x) { return x[0] === 'hoch'; }); }).length;
var nio = AB_CHECK.filter(function (c, i) { return (a.check[i] || {}).st === 'nio'; }).length;
var h = '
Arbeiten mit Absturzgefahr
' +
'
Arbeitsplätze mit Absturzgefahr erfassen, Schutzmaßnahme nach der Rangfolge der TRBS 2121 festlegen und die Organisation prüfen. Ergebnis als PDF, abgelegt unter Dokumente.
Arbeitsplätze mit Absturzgefahr ' + a.plaetze.length + '▾
' +
'
Rangfolge (TRBS 2121 Teil 1): 1. Absturzsicherung bzw. Gerüst/Arbeitsbühne → 2. Auffangeinrichtung → 3. PSA gegen Absturz. Leitern nur bei geringer Gefährdung und kurzer Dauer.
' +
a.plaetze.map(function (p, i) {
var b = 'absturz.plaetze.' + i, hw = abHinweise(p), psa = p.massn === 'psaga';
return '
';
}
function asaNeu() {
var el = document.getElementById('asaPunkte');
if (el) el.outerHTML = htmlAsaPunkte(); else rendere(aktiv, true);
}
function asaStatus(i, st) {
var p = asaPunkte()[i]; if (!p || !asaMangel(p.ref)) return;
sv(p.ref + '.status', st);
if (st === 'erledigt' && !gv(p.ref + '.umgesetzt')) sv(p.ref + '.umgesetzt', asa().datum || heute());
gerendert.bericht = false; markiereGeaendert(); asaNeu();
}
function asaMkPdf() {
var jsPDFctor = (window.jspdf && window.jspdf.jsPDF) || window.jsPDF;
if (!jsPDFctor) { toast('PDF-Bibliothek nicht geladen'); return; }
var l = asaPunkte();
if (!l.length) { toast('Noch keine besprochenen Punkte'); return; }
var a = asa(), doc = new jsPDFctor({ unit: 'mm', format: 'a4', orientation: 'landscape' }), B = doc.internal.pageSize.getWidth(), H = doc.internal.pageSize.getHeight(), gez = {};
function band() {
var s = doc.internal.getCurrentPageInfo().pageNumber; if (gez[s]) return; gez[s] = true;
doc.setFillColor(34, 31, 31); doc.rect(0, 0, B, 22, 'F'); doc.setFillColor(243, 156, 31); doc.rect(0, 22, B, 1.4, 'F');
doc.setTextColor(243, 156, 31); doc.setFont('helvetica', 'bold'); doc.setFontSize(12); doc.text('ZimmAteC e.K.', 12, 10);
doc.setTextColor(255, 255, 255); doc.setFont('helvetica', 'normal'); doc.setFontSize(8); doc.text('Arbeitssicherheit ist kein Luxus', 12, 15.5);
doc.setFont('helvetica', 'bold'); doc.setFontSize(12); doc.text(pdfTxt('Maßnahmenkatalog zur ASA-Sitzung'), B - 12, 10, { align: 'right' });
doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.text(pdfTxt((gv('kopf.firma') || '') + (a.datum ? ' · Sitzung ' + deDat(a.datum) : '')), B - 12, 16, { align: 'right' });
doc.setTextColor(0);
}
band();
doc.autoTable({
head: [['Nr.', 'Mangel / Maßnahme', 'Kapitel', 'Prio', 'Stand / Beschluss ASA', 'Verantwortlich', 'Termin', 'Status', 'Umgesetzt'].map(pdfTxt)],
body: l.map(function (p, i) { var z = asaZeile(p); return [String(i + 1), z.text + (z.bem ? '\nMaßnahme: ' + z.bem : '') + (p.neu ? '\n(neu aus ASA-Sitzung)' : ''), z.kapitel, z.prio, p.beschluss || '', z.verantwortlich, deDat(z.termin), z.status, deDat(z.umgesetzt)].map(function (v) { return pdfTxt(String(v || '')); }); }),
startY: 30, margin: { left: 12, right: 12, top: 30, bottom: 14 }, styles: { fontSize: 8, cellPadding: 1.6, overflow: 'linebreak', lineColor: [214, 209, 201], lineWidth: 0.1 },
headStyles: { fillColor: FARBE.bl, textColor: 255, fontStyle: 'bold' }, alternateRowStyles: { fillColor: [247, 246, 243] },
columnStyles: { 0: { cellWidth: 9 }, 2: { cellWidth: 42 }, 3: { cellWidth: 14 }, 4: { cellWidth: 55 }, 5: { cellWidth: 30 }, 6: { cellWidth: 19 }, 7: { cellWidth: 18 }, 8: { cellWidth: 19 } },
didParseCell: function (d) { if (d.section === 'body' && d.column.index === 7) { var v = String(d.cell.raw); d.cell.styles.fontStyle = 'bold'; d.cell.styles.textColor = v === 'erledigt' ? FARBE.ok : v === 'offen' ? FARBE.bad : FARBE.or; } },
didDrawPage: band
});
var n = doc.internal.getNumberOfPages(), i;
for (i = 1; i <= n; i++) { doc.setPage(i); doc.setFontSize(7.5); doc.setTextColor(120); doc.text(pdfTxt('ZimmAteC e.K. · Waghäusel · Maßnahmenkatalog zur ASA-Sitzung nach § 11 ASiG'), 12, H - 6); doc.text('Seite ' + i + ' von ' + n, B - 12, H - 6, { align: 'right' }); }
var name = 'Massnahmenkatalog_ASA_' + String(gv('kopf.firma') || 'Firma').replace(/[^\wäöüÄÖÜß-]+/g, '_') + '_' + (a.datum || heute()) + '.pdf';
doc.save(name);
try { dokAblegen(doc.output('blob'), name, 'asa', 'ASA-Sitzung ' + (a.datum ? deDat(a.datum) : ''), 'Maßnahmenkatalog zur ASA-Sitzung erstellt'); } catch (e) { toast('Maßnahmenkatalog erstellt'); }
}
/* Zeilen für das ASA-Protokoll (PDF) */
function asaPdfZeilen() {
return asaPunkte().map(function (p, i) {
var z = asaZeile(p);
return [String(i + 1), z.text + (z.bem ? '\nMaßnahme: ' + z.bem : ''), p.beschluss || '', z.verantwortlich || '', deDat(z.termin), z.status].map(function (v) { return pdfTxt(String(v || '')); });
});
}
document.addEventListener('click', function (e) {
var t = e.target, x;
if (!t.closest) return;
if (t.closest('[data-asawahl]')) { ASA_WAHL.offen = true; asaNeu(); return; }
if (t.closest('[data-asawahlzu]')) { ASA_WAHL.offen = false; asaNeu(); return; }
if (t.closest('[data-asapickok]')) {
var l = asaPunkte(), n = 0;
Array.prototype.forEach.call(document.querySelectorAll('[data-asapick]:checked'), function (cb) { l.push({ ref: cb.getAttribute('data-asapick'), text: '', beschluss: '' }); n++; });
ASA_WAHL.offen = false; markiereGeaendert(); asaNeu(); toast(n + ' Punkte übernommen'); return;
}
if (t.closest('[data-asaalleoffen]')) {
var drin = {}, l2 = asaPunkte(), n2 = 0; l2.forEach(function (p) { drin[p.ref] = true; });
maengel().forEach(function (m) { if (m.status !== 'erledigt' && !drin[m.ref]) { l2.push({ ref: m.ref, text: '', beschluss: '' }); n2++; } });
markiereGeaendert(); asaNeu(); toast(n2 + ' offene Punkte übernommen'); return;
}
if ((x = t.closest('[data-asaerl]'))) { asaStatus(+x.getAttribute('data-asaerl'), 'erledigt'); return; }
if ((x = t.closest('[data-asaweg]'))) { var p = asaPunkte()[+x.getAttribute('data-asaweg')]; if (p) { p.text = p.text || asaZeile(p).text; asaPunkte().splice(+x.getAttribute('data-asaweg'), 1); markiereGeaendert(); asaNeu(); } return; }
if (t.closest('[data-asaneu]')) {
var txt = (document.getElementById('asaNeuText') || {}).value || '';
if (!txt.trim()) { toast('Bitte die Maßnahme eintragen'); return; }
var k = ((document.getElementById('asaNeuKap') || {}).value || '').split('|'), mod = k[0], gi = k[1];
if (!MODULE[mod]) return;
if (!S.eigene) S.eigene = {}; if (!S.eigene[mod]) S.eigene[mod] = {}; if (!S.eigene[mod][gi]) S.eigene[mod][gi] = [];
var id = 'e' + gi + '_' + Date.now().toString(36).slice(-6), b = 'check.' + mod + '.' + id;
S.eigene[mod][gi].push({ id: id, text: txt.trim() });
sv(b + '.b', 'nio'); sv(b + '.status', 'offen'); sv(b + '.prio', (document.getElementById('asaNeuPrio') || {}).value || 'mittel');
sv(b + '.verantwortlich', (document.getElementById('asaNeuWer') || {}).value || ''); sv(b + '.termin', (document.getElementById('asaNeuBis') || {}).value || '');
sv(b + '.bem', 'Beschluss ASA-Sitzung' + (asa().datum ? ' vom ' + deDat(asa().datum) : ''));
asaPunkte().push({ ref: b, text: txt.trim(), beschluss: 'neu beschlossen', neu: true });
gerendert[mod] = false; gerendert.bericht = false; markiereGeaendert(); asaNeu(); toast('Maßnahme in den Maßnahmenkatalog aufgenommen'); return;
}
if (t.closest('[data-asamkpdf]')) asaMkPdf();
});
document.addEventListener('change', function (e) {
var el = e.target;
if (!el || !el.hasAttribute) return;
if (el.hasAttribute('data-asast')) { asaStatus(+el.getAttribute('data-asast'), el.value); return; }
if (el.hasAttribute('data-asanuroffen')) { ASA_WAHL.nurOffen = el.checked; asaNeu(); }
});
var asaQT = null;
document.addEventListener('input', function (e) {
var el = e.target;
if (!el || !el.hasAttribute || !el.hasAttribute('data-asaq')) return;
ASA_WAHL.q = el.value; clearTimeout(asaQT);
asaQT = setTimeout(function () { asaNeu(); var n = document.querySelector('[data-asaq]'); if (n) { n.focus(); n.setSelectionRange(n.value.length, n.value.length); } }, 250);
});
/* ---------------------------------------------------------------
Gefahrstoffe: GESTIS-Verknüpfung, Sicherheitsdatenblatt (PDF) per
KI auslesen, Zusammenlagerungscheck als Schaltfläche.
GBU: Art der Beurteilung (Tätigkeit / Arbeitsmittel / Gefahrstoff),
GESTIS bei Gefahrstoff, nach dem PDF Betriebsanweisung anbieten.
--------------------------------------------------------------- */
var GESTIS_SUCHE = 'https://gestis.dguv.de/search';
function gestisUrl(zvg) { var z = String(zvg || '').replace(/\D/g, ''); return z ? 'https://gestis.dguv.de/data?name=' + ('000000' + z).slice(-6) : GESTIS_SUCHE; }
function gestisOeffnen(zvg, suchtext) {
var url = gestisUrl(zvg);
window.open(url, '_blank', 'noopener');
if (!String(zvg || '').replace(/\D/g, '') && suchtext) {
try { navigator.clipboard.writeText(suchtext).then(function () { toast('„' + suchtext + '“ kopiert – in GESTIS ins Suchfeld einfügen'); }); }
catch (e) { toast('In GESTIS nach „' + suchtext + '“ suchen'); }
}
}
/* Gefahrstoff-Zeile: CAS, ZVG-Nr. und GESTIS */
var gs2Zeile = htmlGefahrstoffZeile;
htmlGefahrstoffZeile = function (i) {
var h = gs2Zeile(i), g = S.gefahrstoffe[i] || {}, b = 'gefahrstoffe.' + i;
var zusatz = feld('CAS-Nummer', b + '.cas', 'text', 'placeholder="z. B. 67-64-1"') +
feld('ZVG-Nr. (GESTIS)', b + '.zvg', 'text', 'inputmode="numeric" placeholder="6-stellig, optional"') +
'';
var p = h.indexOf('');
return p >= 0 ? h.slice(0, p) + zusatz + h.slice(p) : h;
};
/* Zusammenlagerungscheck als Schaltfläche */
var GS_LAGER_OFFEN = false;
var gs2Lager = htmlGsLager;
htmlGsLager = function () {
var knopf = '
' +
'' +
'
';
return knopf + (GS_LAGER_OFFEN || (S.gsLager && S.gsLager.erg && S.gsLager.offen) ? gs2Lager() : '');
};
/* Sicherheitsdatenblätter (PDF) für den KI-Assistenten Gefahrstoffe */
var KI_SDB = [];
function kiSdbLaden(files) {
var st = kiSt('gefahrstoffe');
Array.prototype.slice.call(files || []).forEach(function (f) {
if (f.type !== 'application/pdf') { toast(f.name + ': bitte PDF wählen'); return; }
if (f.size > 8 * 1048576) { toast(f.name + ': größer als 8 MB'); return; }
if (st.bilder.length >= 3) { toast('Höchstens 3 Dateien je Analyse'); return; }
var r = new FileReader();
r.onload = function () { st.bilder.push(r.result); KI_SDB.push({ blob: f, name: f.name }); st.offen = true; kiNeu('gefahrstoffe'); };
r.readAsDataURL(f);
});
}
document.addEventListener('change', function (e) {
var el = e.target;
if (el && el.hasAttribute && el.hasAttribute('data-kisdb')) { kiSdbLaden(el.files); el.value = ''; }
});
document.addEventListener('click', function (e) {
var t = e.target, x;
if (!t.closest) return;
if ((x = t.closest('[data-gestis]'))) { var g = S.gefahrstoffe[+x.getAttribute('data-gestis')] || {}; gestisOeffnen(g.zvg, g.cas || g.bez); return; }
if (t.closest('[data-gestisallg]')) { window.open(GESTIS_SUCHE, '_blank', 'noopener'); return; }
if ((x = t.closest('[data-gestisname]'))) { gestisOeffnen(x.getAttribute('data-gestiszvg'), x.getAttribute('data-gestisname')); return; }
if (t.closest('[data-gslagerauf]')) { GS_LAGER_OFFEN = !GS_LAGER_OFFEN; if (S.gsLager) S.gsLager.offen = GS_LAGER_OFFEN; rendere('gefahrstoffe', true); var c = document.getElementById('gsLager'); if (c) c.scrollIntoView({ block: 'start' }); return; }
if ((x = t.closest('[data-gbuba]'))) { gbuBaErstellen(x.getAttribute('data-gbuba') === 'ki'); return; }
if (t.closest('[data-gbubazu]')) { GBU_BA_ANGEBOT = false; rendere('gbu', true); }
});
function kiSdbZuordnen(wahl) {
if (!KI_SDB.length || !wahl.length) { KI_SDB = []; return; }
wahl.forEach(function (v, i) {
var d = KI_SDB[KI_SDB.length === wahl.length ? i : 0];
if (d && v.felder && v.felder.bez) dokAblegen(d.blob, KI_SDB.length === wahl.length || wahl.length === 1 ? d.name : v.felder.bez + ' – ' + d.name, 'sdb', v.felder.bez, 'Sicherheitsdatenblatt abgelegt');
});
KI_SDB = [];
}
/* GBU: Gefahrstoff aus dem Verzeichnis, GESTIS, Betriebsanweisung anbieten */
var GBU_BA_ANGEBOT = false;
function gbuStoff() {
var o = String(gbuKopf().objekt || '').toLowerCase();
return o ? (S.gefahrstoffe || []).filter(function (g) { return String(g.bez || '').toLowerCase() === o; })[0] || null : null;
}
function htmlGbuStoffInfo() { return '
' + htmlGbuStoffInfoInnen() + '
'; }
document.addEventListener('change', function (e) {
var el = e.target;
if (el && el.getAttribute && el.getAttribute('data-k') === 'gbuKopf.objekt') { sv('gbuKopf.objekt', el.value); var bx = document.getElementById('gbuStoffBox'); if (bx) bx.innerHTML = htmlGbuStoffInfoInnen(); }
});
var GBU_SDB = { laeuft: false, meldung: '', fehler: '' };
function htmlGbuStoffInfoInnen() {
var k = gbuKopf();
if (k.bezug !== 'stoff') return '';
var g = gbuStoff(), gs = kiSt('gbu'), sdbDrin = gs.bilder.some(function (s) { return /^data:application\/pdf/.test(s); });
var werkzeuge = '
' +
'' +
'' +
(sdbDrin ? '' : '') +
'
' +
(GBU_SDB.meldung ? '
' + esc(GBU_SDB.meldung) + '
' : '') +
(GBU_SDB.fehler ? '
' + esc(GBU_SDB.fehler) + '
' : '');
if (!k.objekt) return '
' + werkzeuge + '
Gefahrstoff aus dem Verzeichnis wählen oder das Sicherheitsdatenblatt hochladen – Produkt, Einstufung und SDB werden übernommen.
';
return '
' + (g ? 'Aus dem Gefahrstoffverzeichnis: ' + esc([g.hersteller, (g.ghs || []).join(' '), (g.h || []).join(', '), g.lgk ? 'LGK ' + g.lgk : '', g.cas ? 'CAS ' + g.cas : ''].filter(Boolean).join(' · ')) + ' '
: 'Nicht im Gefahrstoffverzeichnis – dort anlegen, damit Daten und Sicherheitsdatenblatt verknüpft sind. ') +
werkzeuge + '
';
}
function htmlGbuBaAngebot() {
if (!GBU_BA_ANGEBOT) return '';
var k = gbuKopf(), was = k.objekt || k.taetigkeit || k.bereich || '';
var art = k.bezug === 'stoff' ? 'Gefahrstoff' : k.bezug === 'arbeitsmittel' ? 'Arbeitsmittel' : 'Tätigkeit';
return '
Betriebsanweisung erstellen? ▾
' +
'
Die Gefährdungsbeurteilung ist erstellt. Nach § 14 GefStoffV bzw. § 12 BetrSichV / § 12 ArbSchG ist eine Betriebsanweisung erforderlich – ' +
'jetzt für ' + esc(art + (was ? ': ' + was : '')) + ' erstellen?
' +
'
';
}
function gbuBaErstellen(mitKi) {
var k = gbuKopf(), art = k.bezug === 'stoff' ? 'gefahrstoff' : k.bezug === 'arbeitsmittel' ? 'arbeitsmittel' : 'taetigkeit';
var was = [k.objekt, k.taetigkeit, k.bereich].filter(Boolean).join(', ');
GBU_BA_ANGEBOT = false; gerendert.gbu = false;
zeigeTab('betriebsanweisungen');
if (!mitKi || !kiVerfuegbar()) {
var b = document.querySelector('[data-banew="' + art + '"]'); if (b) b.click();
if (mitKi) toast('KI nur im Online-Portal – Betriebsanweisung manuell angelegt');
return;
}
BAKI.art = art; BAKI.text = was; baKiNeu();
var el = document.getElementById('ki-ba'); if (el) { el.classList.remove('closed'); el.scrollIntoView({ block: 'start' }); }
baKiLos();
}
if (typeof DOK_PANELS !== 'undefined' && DOK_PANELS.indexOf('gefahrstoffe') < 0) DOK_PANELS.push('gefahrstoffe', 'gbu');
/* SDB direkt in der Gefährdungsbeurteilung: Gefahrstoff anlegen/ergänzen, SDB ablegen, PDF für KI-Gefährdungen bereitstellen */
function gbuSdbBox() { var bx = document.getElementById('gbuStoffBox'); if (bx) bx.innerHTML = htmlGbuStoffInfoInnen(); }
function gbuSdbLaden(f) {
if (!f || f.type !== 'application/pdf') { toast('Bitte das Sicherheitsdatenblatt als PDF wählen'); return; }
if (f.size > 8 * 1048576) { toast('PDF größer als 8 MB'); return; }
var r = new FileReader();
r.onload = async function () {
var pdf = r.result, gs = kiSt('gbu');
gs.bilder = gs.bilder.filter(function (s) { return !/^data:application\/pdf/.test(s); }).slice(0, 2); gs.bilder.push(pdf);
if (!kiVerfuegbar()) {
dokAblegen(f, f.name, 'sdb', gbuKopf().objekt || '', 'Sicherheitsdatenblatt abgelegt');
GBU_SDB.meldung = 'SDB abgelegt. Das automatische Auslesen steht im Online-Kundenportal zur Verfügung.'; gbuSdbBox(); return;
}
GBU_SDB.laeuft = true; GBU_SDB.fehler = ''; GBU_SDB.meldung = ''; gbuSdbBox();
var st = kiSt('gefahrstoffe'); st.bilder = [pdf]; st.text = gbuKopf().taetigkeit || ''; st.erg = null; st.fehler = '';
KI_SDB = [{ blob: f, name: f.name }];
try { await kiAnalyse('gefahrstoffe'); } catch (e) { st.fehler = String(e && e.message || e); }
GBU_SDB.laeuft = false;
if (!st.erg || !st.erg.liste || !st.erg.liste.length) { GBU_SDB.fehler = st.fehler || 'Aus dem Sicherheitsdatenblatt konnte kein Gefahrstoff gelesen werden.'; KI_SDB = []; gbuSdbBox(); return; }
st.erg.liste.forEach(function (v, i) { v.an = i === 0; });
var v0 = st.erg.liste[0], bez = v0.felder && v0.felder.bez;
kiUebernehmen('gefahrstoffe');
var k = gbuKopf();
if (bez) k.objekt = bez;
if (!k.taetigkeit && bez) k.taetigkeit = 'Tätigkeiten mit ' + bez;
gs.offen = true;
GBU_SDB.meldung = (bez ? '„' + bez + '“ ' : 'Gefahrstoff ') + 'übernommen und im Gefahrstoffverzeichnis ergänzt, SDB unter Dokumente abgelegt. Mit „Gefährdungen aus dem SDB vorschlagen“ ermittelt die KI Gefährdungen und Schutzmaßnahmen für diese Beurteilung.';
markiereGeaendert(); rendere('gbu', true);
};
r.readAsDataURL(f);
}
document.addEventListener('change', function (e) {
var el = e.target;
if (el && el.hasAttribute && el.hasAttribute('data-gbusdb')) { gbuSdbLaden(el.files && el.files[0]); el.value = ''; }
});
document.addEventListener('click', function (e) {
var t = e.target;
if (!t.closest || !t.closest('[data-gbusdbki]')) return;
var c = document.getElementById(kiElId('gbu')); if (c) { c.classList.remove('closed'); c.scrollIntoView({ block: 'start' }); }
kiAnalyse('gbu');
});
/* GBU: Arbeitsbereich und Tätigkeit mit Vorschlagsliste und eigenen Einträgen */
var GBU_VORSCHLAG = {
bereich: ['Büro / Verwaltung', 'Werkstatt', 'Produktion / Fertigung', 'Montage', 'Lager / Logistik', 'Wareneingang / Versand', 'Instandhaltung / Technik',
'Schweißerei', 'Lackiererei', 'Holzbearbeitung', 'Metallbearbeitung', 'Labor', 'Küche / Kantine', 'Sozial- und Sanitärräume', 'Technikräume / Heizung',
'Außenbereich / Hof', 'Fuhrpark / Fahrzeuge', 'Baustelle', 'Verkauf / Kundenbereich', 'Reinigung', 'Home-Office / mobiles Arbeiten'],
taetigkeit: ['Bildschirmarbeit', 'Heben und Tragen von Lasten', 'Gabelstapler fahren', 'Kommissionieren / Lagern', 'Arbeiten mit Leitern und Tritten', 'Arbeiten auf Gerüsten',
'Arbeiten mit Hubarbeitsbühnen', 'Arbeiten mit Absturzgefahr', 'Schweißen und Schneiden', 'Schleifen / Trennschleifen', 'Bohren, Sägen, Fräsen', 'Arbeiten an Maschinen und Anlagen',
'Umgang mit Gefahrstoffen', 'Lackieren / Beschichten', 'Reinigungsarbeiten', 'Instandhaltung und Wartung', 'Elektrotechnische Arbeiten', 'Arbeiten in engen Räumen und Behältern',
'Kranarbeiten / Anschlagen von Lasten', 'Montagearbeiten', 'Erdarbeiten', 'Abbrucharbeiten', 'Dacharbeiten', 'Führen von Fahrzeugen (Dienstfahrten)',
'Arbeiten im Freien (Hitze, Kälte, UV)', 'Alleinarbeit', 'Kundenkontakt', 'Nacht- und Schichtarbeit']
};
function gbuListe(k) {
if (!S.gbuListen) S.gbuListen = { bereich: [], taetigkeit: [] };
if (!Array.isArray(S.gbuListen[k])) S.gbuListen[k] = [];
return S.gbuListen[k];
}
function gbuOptionen(k) {
var out = [], dazu = function (n) { n = String(n || '').trim(); if (n && out.indexOf(n) < 0) out.push(n); };
gbuListe(k).forEach(dazu);
try { (S.gbuArchiv || []).forEach(function (a) { dazu(a && a.kopf && a.kopf[k]); }); } catch (e) {}
if (k === 'bereich') try { bereichListe().forEach(dazu); } catch (e) {}
GBU_VORSCHLAG[k].forEach(dazu);
return out;
}
GBU_VORSCHLAG.am = ['Gabelstapler', 'Mitgänger-Flurförderzeug / Hubwagen', 'Hubarbeitsbühne', 'Anlegeleiter', 'Stehleiter / Tritt', 'Fahrgerüst', 'Hallenkran / Brückenkran',
'Anschlagmittel', 'Regalanlage', 'Winkelschleifer', 'Bohrmaschine', 'Kreissäge', 'Bandsäge', 'Drehmaschine', 'Fräsmaschine', 'Presse', 'Schweißgerät',
'Kompressor / Druckluftanlage', 'Hochdruckreiniger', 'Ortsveränderliche elektrische Betriebsmittel', 'Kraftbetätigte Tore und Türen', 'Lkw / Transporter', 'Bildschirmarbeitsplatz'];
GBU_VORSCHLAG.stoff = ['Aceton', 'Isopropanol', 'Bremsenreiniger', 'Kühlschmierstoff', 'Hydrauliköl', 'Dieselkraftstoff', 'Ottokraftstoff (Benzin)', 'Flüssiggas (Propan)', 'Acetylen',
'Sauerstoff', 'Lacke und Verdünnungen', 'Epoxidharz', 'Silikon / Dichtstoffe', 'Reinigungsmittel alkalisch', 'Salzsäure', 'Zementhaltige Produkte', 'Quarzhaltiger Staub',
'Holzstaub', 'Schweißrauch', 'Dieselmotoremissionen'];
function gbuObjektArt() { var b = gbuKopf().bezug; return b === 'stoff' ? 'stoff' : b === 'arbeitsmittel' ? 'am' : ''; }
function htmlGbuAuswahlFeld(k) {
var ob = k === 'objekt', art = ob ? gbuObjektArt() : k;
if (ob && !art) return '';
var label = k === 'bereich' ? 'Arbeitsbereich' : k === 'taetigkeit' ? 'Tätigkeit' : art === 'stoff' ? 'Gefahrstoff' : 'Arbeitsmittel';
var wert = gv('gbuKopf.' + k), eigen = gbuListe(art);
var vorhanden = ob ? gbuObjektListe().filter(function (o) { return o !== wert || (art === 'stoff' ? (S.gefahrstoffe || []).some(function (g) { return g.bez === o; }) : true); }) : [];
var vor = (ob ? GBU_VORSCHLAG[art] : gbuOptionen(k)).filter(function (o) { return eigen.indexOf(o) < 0 && vorhanden.indexOf(o) < 0; });
var opt = function (o) { return ''; };
return '
' +
'' +
'' +
(ob && art === 'stoff' ? '
Neue Gefahrstoffe werden zugleich im Gefahrstoffverzeichnis angelegt.
' : '') + '
';
}
function gbuEintragNeu(k, wert) {
wert = String(wert || '').trim(); if (!wert) return;
var art = k === 'objekt' ? gbuObjektArt() : k;
var l = gbuListe(art); if (l.indexOf(wert) < 0) l.push(wert);
if (art === 'stoff' && !(S.gefahrstoffe || []).some(function (g) { return String(g.bez).toLowerCase() === wert.toLowerCase(); })) {
try { var x = VORLAGEN.gefahrstoffe(); x.bez = wert; S.gefahrstoffe.push(x); gerendert.gefahrstoffe = false; } catch (e) {}
}
sv('gbuKopf.' + k, wert); markiereGeaendert(); rendere('gbu', true); toast('„' + wert + '“ hinzugefügt');
}
document.addEventListener('change', function (e) {
var el = e.target;
if (!el || !el.hasAttribute || !el.hasAttribute('data-gbuwahl')) return;
var k = el.getAttribute('data-gbuwahl'), box = el.parentNode.querySelector('[data-gbuneubox]');
if (el.value === '__neu') { if (box) { box.hidden = false; var i = box.querySelector('input'); if (i) i.focus(); } el.value = gv('gbuKopf.' + k) || ''; return; }
if (box) box.hidden = true;
sv('gbuKopf.' + k, el.value); var inp = el.parentNode.querySelector('[data-k="gbuKopf.' + k + '"]'); if (inp) inp.value = el.value;
if (k === 'objekt') { var bx = document.getElementById('gbuStoffBox'); if (bx) bx.innerHTML = htmlGbuStoffInfoInnen(); }
markiereGeaendert();
});
document.addEventListener('click', function (e) {
var b = e.target.closest && e.target.closest('[data-gbuneuok]');
if (!b) return;
var k = b.getAttribute('data-gbuneuok'), i = b.parentNode.querySelector('input');
gbuEintragNeu(k, i && i.value);
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && e.target && e.target.hasAttribute && e.target.hasAttribute('data-gbuneuwert')) { e.preventDefault(); gbuEintragNeu(e.target.getAttribute('data-gbuneuwert'), e.target.value); }
});
/* Kataster ↔ Gefährdungsbeurteilung: je Arbeitsmittel / Gefahrstoff GBU durchführen und verknüpfen */
function gbuFuerTreffer(art, bez) {
var b = String(bez || '').trim().toLowerCase();
if (!b) return [];
return gbuArchiv().filter(function (e) { return e.bezug === art && String(e.objekt || '').trim().toLowerCase() === b; })
.sort(function (a, c) { return String(c.datum || '').localeCompare(String(a.datum || '')); });
}
function gbuFuerStatus(art, bez) {
var t = gbuFuerTreffer(art, bez), k = gbuKopf();
if (t.length) return { da: true, text: 'GBU ' + (deDat(t[0].datum) || '') + (t[0].hoch ? ' · ' + t[0].hoch + '× hoch' : '') };
if (k.bezug === art && String(k.objekt || '').toLowerCase() === String(bez || '').toLowerCase() && gbuStandDaten().anzahl) return { da: true, text: 'GBU in Bearbeitung' };
return { da: false, text: 'GBU fehlt' };
}
function htmlGbuFuerKnopf(art, bez, bereich) {
if (!bez) return '';
var s = gbuFuerStatus(art, bez);
return '';
}
/* aktuelle Beurteilung still ablegen, damit nichts verloren geht */
function gbuStillAblegen() {
var k = gbuKopf(), z = gbuStandDaten();
if (!z.anzahl) return;
if (!k.bereich && !k.taetigkeit && !k.objekt) k.taetigkeit = 'Beurteilung vom ' + deDat(heute());
var alt = gbuArchiv().filter(function (e) { return e.bezug === k.bezug && (e.objekt || '') === (k.objekt || '') && (e.bereich || '') === (k.bereich || '') && (e.taetigkeit || '') === (k.taetigkeit || ''); })[0];
var t = toast; toast = function () {}; var r = rendere; rendere = function () {};
try { gbuSpeichern(alt ? alt.id : undefined); } finally { toast = t; rendere = r; }
}
function gbuFuer(art, bez, bereich) {
var k = gbuKopf();
var gleich = k.bezug === art && String(k.objekt || '').toLowerCase() === String(bez).toLowerCase();
if (!gleich) {
gbuStillAblegen();
var e = gbuFuerTreffer(art, bez)[0];
if (e) {
S.gbuKopf = JSON.parse(JSON.stringify(e.daten.gbuKopf || {})); S.gbu = JSON.parse(JSON.stringify(e.daten.gbu || {}));
S.gbuInstanzen = JSON.parse(JSON.stringify(e.daten.gbuInstanzen || {})); S.gbuMassnahmen = JSON.parse(JSON.stringify(e.daten.gbuMassnahmen || {}));
toast('Gefährdungsbeurteilung „' + bez + '“ geladen');
} else {
S.gbuKopf = { bereich: bereich || '', taetigkeit: (art === 'stoff' ? 'Tätigkeiten mit ' : 'Arbeiten mit ') + bez, bezug: art, objekt: bez, datum: heute(), titelbild: '' };
S.gbu = {}; S.gbuInstanzen = {};
toast('Neue Gefährdungsbeurteilung für „' + bez + '“ – Gefährdungen auswählen oder KI nutzen');
}
markiereGeaendert();
}
gerendert.gbu = false;
zeigeTab('gbu');
try { zaehlerAktualisieren(); } catch (x) {}
window.scrollTo(0, 0);
}
document.addEventListener('click', function (e) {
var b = e.target.closest && e.target.closest('[data-gbufuer]');
if (!b) return;
e.preventDefault(); e.stopPropagation();
gbuFuer(b.getAttribute('data-gbufuer'), b.getAttribute('data-gbuobj'), b.getAttribute('data-gbuber'));
}, true);
/* Knöpfe in Kataster- und Gefahrstoffzeilen */
var gbuKatZeile = htmlKatasterZeile;
htmlKatasterZeile = function (e) {
var h = gbuKatZeile(e), p = h.indexOf('');
return p >= 0 ? h.slice(0, p + 21) + htmlGbuFuerKnopf('arbeitsmittel', e.bez, e.bereich || e.ort) + h.slice(p + 21) : h;
};
var gbuGsZeile = htmlGefahrstoffZeile;
htmlGefahrstoffZeile = function (i) {
var h = gbuGsZeile(i), g = S.gefahrstoffe[i] || {}, p = h.indexOf('