/* Účtovníček — Nastavenia: firma, číselné rady, záloha, ukážkové dáta, o appke */ function PageSettings({ route, setToast, mode }) { const initial = (route || '').split(':')[1] || 'company'; const [tab, setTab] = React.useState(initial); React.useEffect(() => { const t = (route || '').split(':')[1]; if (t) setTab(t); }, [route]); const tabs = [['company', 'Firma'], ['series', 'Číselné rady']]; if (mode === 'podvojne') tabs.push(['import', 'Import z Profit365']); tabs.push(['flexi', 'Import z Flexi']); if (mode === 'danova') tabs.push(['danova', 'Import zo zošita']); tabs.push(['backup', 'Záloha'], ['account', 'Účet']); if (mode === 'podvojne') tabs.push(['demo', 'Ukážkové dáta']); tabs.push(['about', 'O appke']); return (

Nastavenia

{(uctStore.ctx.company() || {}).name} · údaje firmy, číslovanie dokladov, import a záloha
{tabs.map(([k, l]) => ( ))}
{tab === 'company' && } {tab === 'series' && } {tab === 'import' && } {tab === 'flexi' && } {tab === 'danova' && } {tab === 'backup' && } {tab === 'account' && } {tab === 'demo' && } {tab === 'about' && }
); } function FlexiTab({ setToast }) { const s = uctStore.settings.get(); const [file, setFile] = React.useState(null); const [rename, setRename] = React.useState(() => uctStore.invoices.list('sales').some(i => i.number.startsWith('VFA-'))); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const [result, setResult] = React.useState(null); const [askConfirm, ConfirmUI] = useConfirm(); const hasVFA = uctStore.invoices.list('sales').some(i => i.number.startsWith('VFA-')); const run = async () => { if (!file) { setError('Vyber súbor XML z Flexi.'); return; } if (!await askConfirm({ title: 'Načítať faktúry z Flexi?', message: `Súbor ${file.name}.\n\nPred importom sa odloží záloha databázy. Faktúry, ktoré tu už sú, sa nezaložia druhýkrát - len sa im doplnia položky a adresa.${rename ? '\n\nDoklady VFA- sa premenujú na VF1- ako vo Flexi.' : ''}`, confirmLabel: 'Načítať' })) return; setBusy(true); setError(null); setResult(null); try { const r = await uctStore.flexi.run(file, rename); setResult(r); setToast('Faktúry z Flexi načítané.'); } catch (e) { setError(e.message); } setBusy(false); }; return (

Import z ABRA Flexi

{s.lastImport ? `Naposledy načítané ${fmt.date(s.lastImport)}` : 'Zatiaľ nič nenačítané'}
setError(null)} />
  1. V klientovi Flexi otvor Vydané faktúry, klikni do zoznamu a stlač Ctrl+A.
  2. Pravým tlačidlom na označené riadky vyber Export XML a ulož súbor (ide do WinStrom/Export).
  3. Súbor vyber tu a klikni Načítať. To isté môžeš urobiť pre Prijaté faktúry.
setFile(e.target.files && e.target.files[0] || null)} /> {hasVFA && }
{result && (

Výsledok importu

záloha pred importom: {result.backupFile || '—'}
nové faktúry {result.created}doplnené {result.updated}už boli {result.skipped}noví partneri {result.partners}{result.renamed > 0 && premenované VFA→VF1 {result.renamed}}
{result.warnings.length > 0 && <>

Upozornenia ({result.warnings.length}):

    {result.warnings.map((m, i) =>
  • {m}
  • )}
}
)} {ConfirmUI}
); } function DanovaTab({ setToast }) { const [file, setFile] = React.useState(null); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const [result, setResult] = React.useState(null); const [askConfirm, ConfirmUI] = useConfirm(); const run = async () => { if (!file) { setError('Vyber súbor JSON z prevodníka zošita.'); return; } if (!await askConfirm({ title: 'Načítať daňovú evidenciu?', message: `Súbor ${file.name}.\n\nPred importom sa odloží záloha. Príjmy načítané zo zošita skôr sa nahradia, ručne zapísané príjmy ostanú. Faktúry, ktoré tu už sú, dostanú rozdelenie príjmu zo zošita.`, confirmLabel: 'Načítať' })) return; setBusy(true); setError(null); setResult(null); try { const r = await uctStore.danova.run(file); setResult(r); setToast('Daňová evidencia načítaná.'); } catch (e) { setError(e.message); } setBusy(false); }; return (

Import zo zošita daňovej evidencie

listy Pohľadávky RRRR a Príjmy RRRR
setError(null)} />

Zošit sa najprv prevedie prevodníkom danova_xlsx_to_json.py (v podkladoch Účtovníčka) na súbor JSON, ten sa vyberie tu. Stĺpce sa čítajú podľa hlavičiek, takže staršie roky s iným rozložením prejdú tiež.

setFile(e.target.files && e.target.files[0] || null)} />
{result && (

Výsledok importu

záloha pred importom: {result.backupFile || '—'}
pohľadávky {result.receivables}z toho nové faktúry {result.created}príjmy {result.incomes}roky {result.years}
{result.warnings.length > 0 && <>

Upozornenia ({result.warnings.length}):

    {result.warnings.map((m, i) =>
  • {m}
  • )}
}
)} {ConfirmUI}
); } function AccountTab({ setToast }) { const u = uctStore.auth.user() || {}; const st = uctStore.auth.state() || {}; if (st.oidc) { return (

Účet

prihlásená ako {u.name || u.login} ({u.login})
Prihlásenie ide cez firemný {st.provider || 'Authentik'}. Heslo, dvojfaktor aj odhlásenie zo všetkých zariadení sa riešia tam, Účtovníček si vlastné heslo nedrží.
); } const [oldPw, setOld] = React.useState(''); const [pw, setPw] = React.useState(''); const [pw2, setPw2] = React.useState(''); const [error, setError] = React.useState(null); const save = async () => { setError(null); if (pw !== pw2) { setError('Nové heslá sa nezhodujú.'); return; } try { await uctStore.auth.changePassword(oldPw, pw); setOld(''); setPw(''); setPw2(''); setToast('Heslo zmenené.'); } catch (e) { setError(e.message); } }; return (

Účet

prihlásená ako {u.name || u.login} ({u.login})
setError(null)} /> setOld(e.target.value)} /> setPw(e.target.value)} /> setPw2(e.target.value)} />
Toto mení len miestne heslo pre prihlásenie bez internetu. Heslo do Diarika sa mení v Diariku; pri ďalšom online prihlásení sa miestne heslo zrovná s ním.
); } function CompanyForm({ setToast, mode }) { const podvojne = mode === 'podvojne'; const [c, setC] = React.useState(() => ({ name: '', ico: '', dic: '', icDph: '', address: '', bank: '', bankName: '', registration: '', email: '', phone: '', vatPayer: false, legalForm: '', unitSize: '', bankAccount: '221001', cashAccount: '211001', ...uctStore.company.get() })); const [s, setS] = React.useState(() => ({ ...uctStore.settings.get() })); const [error, setError] = React.useState(null); const set = (k) => (e) => setC({ ...c, [k]: e.target.value }); const save = async () => { setError(null); try { await uctStore.company.save(c); await uctStore.settings.save(s); setToast('Nastavenia firmy uložené.'); } catch (e) { setError(e.message); } }; return (

Účtovná jednotka

Tieto údaje sa tlačia na faktúry. Polia sú rovnaké ako v Mzdáriku.
setError(null)} />
{podvojne && } {podvojne &&
}
Platiteľ DPH
Dnes neplatiteľ. Keď sa to zmení, zapni - faktúry dostanú DPH.
setS({ ...s, defaultDueDays: Math.max(0, Math.round(v)) })} />
); } function SeriesTab({ setToast }) { const [rows, setRows] = React.useState(() => uctStore.series.list().map(s => ({ ...s }))); const [error, setError] = React.useState(null); const update = (i, k, v) => setRows(rows.map((r, j) => j === i ? { ...r, [k]: v } : r)); const save = async (r) => { setError(null); try { await uctStore.series.update(r); setToast('Číselný rad uložený.'); } catch (e) { setError(e.message); } }; return (

Číselné rady

Tvar čísla: {'{NNNN}'} je poradie, {'{RRRR}'} rok. Variabilný symbol = predpona + poradie + rok (napr. 1 + 0025 + 2026 = 100252026).
setError(null)} /> {rows.map((r, i) => ( ))} {rows.length === 0 && }
RadRokTvar číslaPredpona VSĎalšie číslo
{r.code}
{r.name}
{r.year} update(i, 'format', e.target.value)} /> update(i, 'vsPrefix', e.target.value.replace(/\D/g, ''))} /> update(i, 'next', Math.max(1, Math.round(v)))} />
Rady vzniknú pri prvom doklade.
); } function BackupTab({ setToast }) { const [error, setError] = React.useState(null); const [askConfirm, ConfirmUI] = useConfirm(); const fileRef = React.useRef(null); const restore = async (e) => { const f = e.target.files && e.target.files[0]; e.target.value = ''; if (!f) return; let obj; try { obj = JSON.parse(await f.text()); } catch { setError('Súbor nie je záloha Účtovníčka (nedá sa prečítať).'); return; } const n = (obj.invoices || []).length, p = (obj.partners || []).length; if (!await askConfirm({ title: 'Obnoviť zo zálohy?', message: `Záloha z ${obj.exported ? fmt.date(obj.exported) : '?'} obsahuje ${n} ${plural(n, 'faktúru', 'faktúry', 'faktúr')} a ${p} ${plural(p, 'partnera', 'partnerov', 'partnerov')}.\n\nVšetko, čo je v appke teraz, sa nahradí obsahom zálohy.`, confirmLabel: 'Obnoviť', danger: true })) return; try { await uctStore.backup.importJSON(obj); setToast('Záloha obnovená.'); setError(null); } catch (err) { setError(err.message); } }; return (

Stiahnuť zálohu

Jeden súbor so všetkým: firma, partneri, osnova, faktúry, doklady, denník, číselné rady.

Ulož si ho napríklad na USB kľúč. Dá sa z neho vrátiť všetko presne tak, ako to bolo.

Obnoviť zo zálohy

Vyber súbor uctovnicek-RRRR-MM-DD.json
setError(null)} />

Pred obnovou sa appka opýta. Obnova nahradí všetko naraz - buď celá prejde, alebo sa nezmení nič.

Automaticky: pri každom spustení si Účtovníček odloží kópiu databázy každej firmy do priečinka ~/.local/share/uctovnicek/zalohy (raz denne, drží 14 posledných). Aktualizácia appky dáta nikdy neprepíše - databázy sú mimo programu. Záloha je za firmu, ktorú máš práve vybranú.
{ConfirmUI}
); } function ImportTab({ setToast }) { const s = uctStore.settings.get(); const [agenda, setAgenda] = React.useState(null); const [osnova, setOsnova] = React.useState(null); const [replace, setReplace] = React.useState(false); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const [result, setResult] = React.useState(null); const [askConfirm, ConfirmUI] = useConfirm(); const run = async () => { if (!agenda) { setError('Vyber súbor Export agendy (xlsx) z Profit365.'); return; } if (!await askConfirm({ title: 'Načítať dáta z Profit365?', message: `Súbor ${agenda.name}${osnova ? ' + osnova ' + osnova.name : ''}.\n\nPred importom sa odloží záloha databázy. Import prejde celý, alebo sa nezmení nič.${replace ? '\n\nPredchádzajúci import z Profit365 sa nahradí.' : ''}`, confirmLabel: 'Načítať' })) return; setBusy(true); setError(null); setResult(null); try { const r = await uctStore.profit365.run({ agenda, osnova }, replace); setResult(r); setToast('Dáta z Profit365 načítané.'); } catch (e) { setError(e.message); } setBusy(false); }; const pick = (setter) => (e) => { setter(e.target.files && e.target.files[0] || null); }; return (

Import z Profit365

{s.lastImport ? `Naposledy načítané ${fmt.date(s.lastImport)}` : 'Zatiaľ nič nenačítané'}
setError(null)} />
  1. V Profit365 otvor Nástroje → Export agendy, nechaj rok bez výberu a klikni Exportovať. Po chvíli príde odkaz na súbor .xlsx.
  2. Súbor vyber tu a klikni Načítať. Prenesú sa partneri, faktúry, bankové výpisy, pokladňa, interné doklady a celý denník.
{result && (

Výsledok importu

záloha pred importom: {result.backupFile || '—'}
partneri {result.partners}účty {result.accounts}vystavené {result.sales}prijaté {result.purchase}výpisy {result.bank}pokladňa {result.cash}interné {result.internal}zápisy denníka {result.journal}
{result.adjusted > 0 &&

{result.adjusted} {plural(result.adjusted, 'faktúra dorovnaná', 'faktúry dorovnané', 'faktúr dorovnaných')} ručnou úhradou podľa saldokonta Profit365 (platba v banke nemala správny VS).

}

Hlavná kniha oproti Profit365: {result.mismatches.length === 0 ? 'všetky účty sedia na cent.' : `${result.mismatches.length} ${plural(result.mismatches.length, 'rozdiel', 'rozdiely', 'rozdielov')}:`}

{result.mismatches.length > 0 &&
    {result.mismatches.map((m, i) =>
  • {m}
  • )}
} {result.warnings.length > 0 && <>

Upozornenia ({result.warnings.length}):

    {result.warnings.map((m, i) =>
  • {m}
  • )}
}
)} {ConfirmUI}
); } function DemoTab({ setToast }) { const s = uctStore.settings.get(); const [busy, setBusy] = React.useState(false); const [error, setError] = React.useState(null); const [askConfirm, ConfirmUI] = useConfirm(); const run = async (fn, msg) => { setBusy(true); setError(null); try { await fn(); setToast(msg); } catch (e) { setError(e.message); } setBusy(false); }; const remove = async () => { if (!await askConfirm({ title: 'Vymazať ukážkové dáta?', message: 'Vymažú sa len záznamy označené ako ukážka. Tvoje vlastné ostanú.', confirmLabel: 'Vymazať', danger: true })) return; run(uctStore.demo.remove, 'Ukážkové dáta vymazané.'); }; return (

Ukážkové dáta

Vymyslení partneri a faktúry, nech je vidno, ako appka vyzerá
setError(null)} />

{s.demoDataPresent ? 'Ukážkové dáta sú vložené. Keď začneš účtovať naozaj, vymaž ich - číselný rad sa potom nastav v záložke Číselné rady.' : 'Ukážkové dáta nie sú vložené.'}

{s.demoDataPresent ? : }
{ConfirmUI}
); } function AboutTab() { const a = uctStore.about(); return (
Účtovníček
Verzia {a.version} · podvojné účtovníctvo, fakturácia a daňová evidencia
Rovnaký základ ako Mzdárik, aby sa neskôr dali prepojiť (zaúčtovanie miezd).
Dáta: ~/.local/share/uctovnicek/ (jedna databáza na firmu, účty v ucty.db)
); }