function db_() { const id = PropertiesService.getScriptProperties().getProperty('DB_SPREADSHEET_ID'); if (!id) throw new Error('Databáze není inicializovaná. Spusť setupSystem().'); return SpreadsheetApp.openById(id); } function sh_(name) { const s = db_().getSheetByName(name); if (!s) throw new Error('Chybí list ' + name); return s; } function rows_(name) { const s = sh_(name); const values = s.getDataRange().getValues(); if (values.length < 2) return []; const headers = values[0].map(String); return values.slice(1).filter(r => r.some(v => v !== '')).map(r => { const o = {}; headers.forEach((h, i) => o[h] = r[i]); return o; }); } function append_(name, obj) { const s = sh_(name); const headers = s.getRange(1,1,1,s.getLastColumn()).getValues()[0]; s.appendRow(headers.map(h => obj[h] !== undefined ? obj[h] : '')); return obj; } function updateBy_(name, key, value, patch) { const s = sh_(name); const data = s.getDataRange().getValues(); if (!data.length) return false; const headers = data[0].map(String); const keyCol = headers.indexOf(key); if (keyCol < 0) throw new Error('Sloupec ' + key + ' neexistuje v ' + name); const row = data.slice(1).findIndex(r => String(r[keyCol]) === String(value)); if (row < 0) return false; Object.keys(patch).forEach(k => { const c = headers.indexOf(k); if (c >= 0) s.getRange(row + 2, c + 1).setValue(patch[k]); }); return true; } function findOne_(name, predicate) { return rows_(name).find(predicate) || null; } function uuid_(prefix) { return (prefix || 'ID') + '_' + Utilities.getUuid().replace(/-/g,'').slice(0,16).toUpperCase(); } function now_() { return new Date(); } function isoDate_(d) { return Utilities.formatDate(new Date(d), CFG.TZ, 'yyyy-MM-dd'); } function period_(d) { return Utilities.formatDate(new Date(d), CFG.TZ, 'yyyy-MM'); } function emailNorm_(email) { return String(email || '').trim().toLowerCase(); } function json_(v) { try { return JSON.stringify(v); } catch(e) { return String(v); } }