/*! * Tectum * Copyright © 2026 Machiel Heinen. * Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming. * Zie LICENTIE.md. */ window.CostSettings = (() => { let overlay = null; const esc = UI.esc; const SJABLOON_BALKEN = [[38, 235], [70, 220]]; function build() { overlay = document.createElement('div'); overlay.id = 'cost-settings-overlay'; overlay.innerHTML = `
Bedragen excl. btw. De CSV's in kosten/ zijn leidend: Toepassen geldt tot herladen; om een wijziging te houden "intern.csv downloaden" en het bestand in kosten/ vervangen, of met "Wijzigingen downloaden" alleen de aangepaste velden (met de standaardwaarde ernaast) opslaan. Prijzen pas je aan in leveranciers.csv (CSV = daaruit, anders noodwaarde). Een veld dat afwijkt van intern.csv is gemarkeerd.
`; document.body.appendChild(overlay); overlay.addEventListener('click', (e) => { if (e.target === overlay) return close(); const btn = e.target.closest('[data-cs]'); if (!btn) return; const a = btn.dataset.cs; if (a === 'cancel') close(); else if (a === 'save') { CostModel.saveSettings(readForm()); close(); } else if (a === 'reset') fill(withLeveranciers(CostModel.defaultValues())); else if (a === 'export') exportIntern(); else if (a === 'export-changes') exportChanges(); else if (a === 'import') overlay.querySelector('[data-file=import]').click(); else if (a === 'lev-load') overlay.querySelector('[data-file=leveranciers]').click(); else if (a === 'lev-clear') CostCsv.reload('leveranciers'); else if (a === 'lev-template') saveFile(CostCsv.template('leveranciers', readForm(), SJABLOON_BALKEN), 'leveranciers.csv'); }); overlay.querySelector('[data-file=import]').addEventListener('change', (e) => { const file = e.target.files[0]; e.target.value = ''; if (!file) return; file.text().then(txt => fill(withLeveranciers(CostModel.importFile(txt)))) .catch(err => alert('Importeren mislukt: ' + err.message)); }); overlay.querySelector('[data-file=leveranciers]').addEventListener('change', (e) => { const file = e.target.files[0]; e.target.value = ''; if (!file) return; file.text().then(txt => CostCsv.loadText('leveranciers', txt, file.name)) .catch(err => alert('Leverancierslijst laden mislukt: ' + err.message)); }); CostCsv.onChange(() => { if (!overlay.classList.contains('open')) return; const v = readForm(); renderForm(); fill(withLeveranciers(v)); }); overlay.addEventListener('input', markChanged); } const levEntry = (g, k) => CostCsv.entry('leveranciers', `${g}.${k}`); const withLeveranciers = (values) => CostCsv.overlay('leveranciers', values); function exportIntern() { saveFile(CostCsv.template('intern', readForm(CostModel.getSettings({ leveranciers: false }))), 'intern.csv'); } function exportChanges() { const text = CostCsv.changes(readForm(CostModel.getSettings({ leveranciers: false })), CostModel.defaultValues()); if (!text) return alert('Geen wijzigingen ten opzichte van intern.csv.'); const d = new Date(), p = (n) => String(n).padStart(2, '0'); saveFile(text, `wijzigingen-intern-${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}.csv`); } function renderStatus() { const when = (st) => st.loadedAt ? new Date(st.loadedAt).toLocaleString('nl-NL', { dateStyle: 'short', timeStyle: 'short' }) : ''; const block = (list, extra) => { const st = CostCsv.status(list); const head = !st.source ? `${esc(st.label)}: geen ${esc(st.url)} gevonden` : `${esc(st.label)}: ${esc(st.name || '')}${st.source === 'tijdelijk' ? ' (tijdelijk ingeladen, tot herladen)' : ''} · ${st.count} waarden · ${esc(when(st))}`; const issues = st.errors.map(e => `
  • ${esc(e)}
  • `) .concat(st.unknown.map(u => `
  • regel ${u.line}: code ${esc(u.code)}${u.label ? ` (${esc(u.label)})` : ''} wordt (nog) niet gebruikt
  • `)); return `
    ${head}${extra ? extra(st) : ''}
    ` + (issues.length ? `
    ${st.errors.length} fout(en), ${st.unknown.length} onbekende code(s)
    ` : '') + '
    '; }; overlay.querySelector('.cs-pricelist').innerHTML = block('intern') + block('leveranciers', (st) => ` ` + `` + (st.source === 'tijdelijk' ? `` : '')); } function renderForm() { renderStatus(); const body = overlay.querySelector('.cs-body'); body.innerHTML = Object.entries(window.COST_DEFAULTS).map(([g, grp]) => `

    ${esc(grp.label)}

    ${Object.entries(grp.items).map(([k, it]) => { const lev = levEntry(g, k), int = CostCsv.entry('intern', `${g}.${k}`), price = CostCsv.isPrice(it); const tip = lev ? `Uit leveranciers.csv${lev.supplier ? ': ' + lev.supplier : ''}${lev.article ? ', art. ' + lev.article : ''}${lev.updated ? ', ' + lev.updated : ''} (regel ${lev.line})` : price ? `${g}.${k}: niet in leveranciers.csv, noodwaarde uit cost-defaults.js` : `code ${g}.${k}${int ? ` (intern.csv regel ${int.line})` : ': niet in intern.csv, noodwaarde uit cost-defaults.js'}`; return ` `; }).join('')}
    `).join(''); } function fill(values) { overlay.querySelectorAll('[data-g]').forEach(el => { el.value = values[el.dataset.g][el.dataset.k]; }); markChanged(); } function readForm(base) { const v = base || CostModel.getSettings(); overlay.querySelectorAll('[data-g]:not(:disabled)').forEach(el => { const it = window.COST_DEFAULTS[el.dataset.g].items[el.dataset.k]; const val = it.options ? el.value : parseFloat(el.value); if (it.options || isFinite(val)) v[el.dataset.g][el.dataset.k] = val; }); return v; } function markChanged() { const def = CostModel.defaultValues(); overlay.querySelectorAll('[data-g]').forEach(el => { const d = def[el.dataset.g][el.dataset.k]; el.closest('.cs-row').classList.toggle('changed', !el.disabled && typeof d === 'number' ? parseFloat(el.value) !== d : el.value !== d); }); } function saveFile(text, name) { const a = document.createElement('a'); a.href = URL.createObjectURL(new Blob([text], { type: 'text/csv' })); a.download = name; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 1000); } function open() { if (!overlay) build(); renderForm(); fill(CostModel.getSettings()); overlay.classList.add('open'); } function close() { if (overlay) overlay.classList.remove('open'); } return { open, close }; })();