/* Účtovníček — Banka (výpisy), Pokladňa (PPD/VPD), Interné doklady: zoznam, formulár s riadkami, detail */ const DOC_META = { bank: { title: 'Banka', sub: 'Bankové výpisy - každý riadok je platba s protiúčtom, úhrady faktúr sa spárujú podľa VS', one: 'výpis', newLabel: 'Nový výpis', icon: 'bank', binder: 'BAN' }, cash: { title: 'Pokladňa', sub: 'Príjmové (PPD) a výdavkové (VPD) pokladničné doklady', one: 'doklad', newLabel: 'Nový pokladničný doklad', icon: 'wallet', binder: 'POK' }, internal: { title: 'Interné doklady', sub: 'Zápisy, ktoré nemajú faktúru ani platbu: mzdy, odpisy, dane, začiatočné stavy', one: 'doklad', newLabel: 'Nový interný doklad', icon: 'book', binder: 'ID' }, }; function PageDocuments({ kind, go, route }) { const meta = DOC_META[kind]; const [, action, routeId] = (route || '').split(':'); const [openId, setOpenId] = React.useState(action === 'open' ? routeId : null); const [creating, setCreating] = React.useState(action === 'new'); const [search, setSearch] = React.useState(''); const [year, setYear] = React.useState(''); const [setToast, Toast] = useToast(); React.useEffect(() => { if (action === 'open') setOpenId(routeId); if (action === 'new') setCreating(true); }, [route]); const all = uctStore.documents.list(kind); const years = [...new Set(all.map(d => String(d.date).slice(0, 4)))].sort().reverse(); const q = search.trim().toLowerCase(); const list = all.filter(d => (!year || String(d.date).startsWith(year)) && (!q || d.number.toLowerCase().includes(q) || (d.title || '').toLowerCase().includes(q) || (d.partnerName || '').toLowerCase().includes(q) || d.lines.some(l => (l.text || '').toLowerCase().includes(q) || (l.vs || '').includes(q)))); const sum = (f) => list.reduce((a, d) => a + (f(d) || 0), 0); const balance = kind === 'bank' ? uctStore.dashboard.get().bank : kind === 'cash' ? uctStore.dashboard.get().cash : null; return (

{meta.title}

{meta.sub}
{balance != null &&
Zostatok
{fmt.eur(balance)}
}
setSearch(e.target.value)} />
{kind === 'internal' ? : <>} {list.map(d => ( setOpenId(d.id)}> {kind === 'internal' ? : <>} ))} {list.length === 0 && } {list.length > 0 && ( {kind === 'internal' ? : <>} )}
ČísloDátum{kind === 'cash' ? 'Účel' : kind === 'bank' ? 'Výpis' : 'Popis'}PartnerSumaPríjemVýdaj
{d.number}{kind === 'cash' && {d.cashType === 'vydaj' ? 'výdaj' : 'príjem'}}{d.diff ? neúplný : null} {fmt.date(d.date)} {d.title || (d.lines[0] && d.lines[0].text) || {d.lines.length} {plural(d.lines.length, 'riadok', 'riadky', 'riadkov')}} {d.partnerName}{fmt.eur(d.total)}{d.income ? fmt.num(d.income) : ''}{d.expense ? fmt.num(d.expense) : ''}

{all.length ? 'Nič sa nenašlo' : `Zatiaľ žiadny ${meta.one}`}

{all.length ? 'Skús iný rok alebo hľadanie.' : `Pridaj prvý tlačidlom „${meta.newLabel}" vpravo hore.`}
{list.length} {plural(list.length, meta.one, kind === 'bank' ? 'výpisy' : 'doklady', kind === 'bank' ? 'výpisov' : 'dokladov')}{fmt.eur(sum(d => d.total))}{fmt.num(sum(d => d.income))}{fmt.num(sum(d => d.expense))}
{creating && { setCreating(false); go(kind); }} onSaved={(d) => { setCreating(false); setOpenId(d.id); setToast(`${d.number} uložený.`); }} />} {openId && !creating && { setOpenId(null); go(kind); }} setToast={setToast} go={go} />} {Toast}
); } /* Príjem/výdaj v riadku: protiúčet DAL = peniaze prišli, protiúčet MD = peniaze odišli. */ function lineIncome(l) { return l.dal || 0; } function lineExpense(l) { return l.md || 0; } function DocumentForm({ kind, document, onClose, onSaved }) { const isNew = !document; const meta = DOC_META[kind]; const [d, setD] = React.useState(() => isNew ? uctStore.documents.blank(kind) : structuredClone(document)); const [preview, setPreview] = React.useState('…'); const [error, setError] = React.useState(null); const [saving, setSaving] = React.useState(false); const money = kind !== 'internal'; React.useEffect(() => { if (!isNew) return; const y = +String(d.date || '').slice(0, 4) || new Date().getFullYear(); const sk = kind === 'bank' ? 'bank' : kind === 'cash' ? (d.cashType === 'vydaj' ? 'cashout' : 'cashin') : 'internal'; uctStore.documents.nextNumber(sk, y).then(r => setPreview(r.number)).catch(() => setPreview('?')); }, [d.date, d.cashType, isNew]); const set = (k, v) => setD(prev => ({ ...prev, [k]: v })); const setLine = (i, patch) => setD(prev => ({ ...prev, lines: prev.lines.map((l, j) => j === i ? { ...l, ...patch } : l) })); const addLine = () => set('lines', [...d.lines, { text: '', account: '', md: 0, dal: 0, partnerId: '', partnerName: '', vs: '' }]); const delLine = (i) => set('lines', d.lines.length > 1 ? d.lines.filter((_, j) => j !== i) : d.lines); const pickLinePartner = (i, p) => setLine(i, { partnerId: p ? p.id : '', partnerName: p ? p.name : '' }); const income = d.lines.reduce((a, l) => a + lineIncome(l), 0); const expense = d.lines.reduce((a, l) => a + lineExpense(l), 0); const sumMD = d.lines.reduce((a, l) => a + (l.md || 0), 0); const sumDAL = d.lines.reduce((a, l) => a + (l.dal || 0), 0); /* Napovedanie faktúry podľa VS: keď riadok má VS neuhradenej faktúry, ponúkni sumu a účet. */ const matchInvoice = (vs) => { if (!vs) return null; const kindWanted = kind === 'cash' && d.cashType === 'vydaj' ? 'purchase' : null; return uctStore.invoices.all().find(i => i.vs === vs && i.status !== 'storno' && (!kindWanted || i.kind === kindWanted)) || null; }; const applyInvoice = (i, inv) => { const rest = uctStore.invoices.rest(inv); const patch = { partnerId: inv.partnerId, partnerName: inv.partnerName, account: inv.account, text: patchText(d.lines[i].text, `Úhrada ${inv.number}`) }; if (inv.kind === 'sales') { patch.dal = rest; patch.md = 0; } else { patch.md = rest; patch.dal = 0; } setLine(i, patch); }; const patchText = (cur, def) => cur && cur.trim() ? cur : def; const save = async () => { setError(null); if (d.lines.some(l => !l.account)) { setError('Každý riadok potrebuje účet.'); return; } if (!money && Math.round((sumMD - sumDAL) * 100) !== 0) { setError(`Interný doklad nie je vyrovnaný: MD ${fmt.num(sumMD)}, DAL ${fmt.num(sumDAL)}.`); return; } setSaving(true); try { const saved = isNew ? await uctStore.documents.create(d) : await uctStore.documents.update(d); onSaved(saved); } catch (e) { setError(e.message); } setSaving(false); }; return ( Dostane číslo {preview} : null} onClose={onClose} footer={<> }> setError(null)} />
set('date', e.target.value)} /> {kind === 'cash' && ( )} {money && set('moneyAccount', v)} prefixes={[kind === 'bank' ? '22' : '21']} />} {kind !== 'bank' && setD(prev => ({ ...prev, partnerId: p ? p.id : '', partnerName: p ? p.name : '' }))} />}
set('title', e.target.value)} placeholder={kind === 'bank' ? 'Výpis č. 8 za august' : kind === 'cash' ? 'Cestovné náhrady - služobná cesta Žilina' : 'Mzdy 10/2026'} />
{money ? 'Pohyby (riadky výpisu)' : 'Zápisy (MD / DAL)'}
{d.lines.map((l, i) => { const inv = matchInvoice(l.vs); return ( ); })}
Text{money ? 'Protiúčet' : 'Účet'}PartnerVS {money ? 'Príjem' : 'MD'}{money ? 'Výdaj' : 'DAL'}
setLine(i, { text: e.target.value })} /> setLine(i, { account: v })} /> setLine(i, { vs: e.target.value.replace(/\D/g, '') })} /> {inv && (inv.account !== l.account || (lineIncome(l) + lineExpense(l)) === 0) && } setLine(i, money ? { dal: v } : { md: v })} /> setLine(i, money ? { md: v } : { dal: v })} />
{money ? <> : <> }
Príjmy{fmt.eur(income)}
Výdaje{fmt.eur(expense)}
Zmena zostatku{fmt.eur(income - expense)}
MD spolu{fmt.eur(sumMD)}
DAL spolu{fmt.eur(sumDAL)}
Rozdiel 0.004 ? 'var(--danger)' : 'var(--accent)'}}>{fmt.eur(sumMD - sumDAL)}