gpt51

가계부 · Wealth Ledger (VND)
Buku Besar Kekayaan
Buku Rekening Rumah Tangga Real-time VND
Masukan cepat Integritas data normal
VND
Total aset
Rp0
Penghematan bulan ini: Rp0
uang tunai
Rp0
akun perusahaan
Rp0
akun pribadi
Rp0

Rencana pengeluaran

Rencana Pengeluaran Mingguan
Rp0 / Rp0
Rencana pengeluaran bulanan
Rp0 / Rp0
VND
VND
Tingkatkan tabungan Anda dengan mengurangi pengeluaran aktual dibandingkan dengan rencana Anda.

Saldo awal

VND
VND
VND
Saldo awal langsung tercermin dalam total kumulatif.
Ini tidak dapat dibatalkan. Kami sarankan Anda membuat cadangan.

Pendapatan/Pengeluaran selama 12 bulan terakhir

tabungan bulanan

Riwayat transaksi

tanggalakunkategoriKategorimemojumlahbekerja
0 kasus
impor: Rp0
pengeluaran: Rp0
tabungan: Rp0
Kebiasaan menciptakan kekayaan. Catatlah hari ini juga ✨
VND
`; const blob = new Blob([html], {type:'application/vnd.ms-excel'}); downloadBlob(blob, `ledger_vnd_${todayStr()}.xls`, 'application/vnd.ms-excel'); } function backupJSON(){ const payload = { settings, tx }; const json = '\uFEFF'+JSON.stringify(payload); downloadBlob(json, `ledger_vnd_backup_${todayStr()}.json`, 'application/json;charset=utf-8;'); } function restoreJSON(e){ const f = e.target.files[0]; if(!f) return; const reader = new FileReader(); reader.onload = function(){ try{ const obj = JSON.parse(reader.result); if(!obj || !Array.isArray(obj.tx) || !obj.settings) throw new Error('bad file'); // minimal validation settings = { ...defaultSettings(), ...obj.settings, budgets: { weekly: toInt(obj.settings?.budgets?.weekly||0), monthly: toInt(obj.settings?.budgets?.monthly||0) }, initBalances:{ cash: toInt(obj.settings?.initBalances?.cash||0), biz: toInt(obj.settings?.initBalances?.biz||0), personal: toInt(obj.settings?.initBalances?.personal||0), } }; tx = obj.tx.map(n=>({ id: n.id || uid(), type: n.type==='in'?'in':'out', account: ['cash','biz','personal'].includes(n.account)?n.account:'cash', amount: toInt(n.amount||0), date: n.date || fmtDateInput(new Date()), category: n.category||'', memo: n.memo||'', createdAt: n.createdAt || Date.now() })); saveSettings(); saveTx(); // Refill inputs els.langSelect.value = settings.lang; applyI18N(); applyTheme(settings.theme); setAmtInput(els.inpWeeklyPlan, settings.budgets.weekly); setAmtInput(els.inpMonthlyPlan, settings.budgets.monthly); setAmtInput(els.initCash, settings.initBalances.cash); setAmtInput(els.initBiz, settings.initBalances.biz); setAmtInput(els.initPersonal, settings.initBalances.personal); drawAll(); showToast(t('imported')); els.restoreFile.value = ''; }catch(err){ alert('Invalid backup file.'); } }; reader.readAsText(f); }// Helpers function escCsv(s){ return (s||'').replace(/[\r\n]+/g,' '); } function csvCell(s){ if(s==null) return ''; const str = String(s); if(/[",\n]/.test(str)) return `"${str.replace(/"/g,'""')}"`; return str; } function downloadBlob(content, filename, mime){ let blob = content instanceof Blob ? content : new Blob([content], {type:mime}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); setTimeout(()=>{ URL.revokeObjectURL(url); a.remove(); }, 100); } function todayStr(){ const d = new Date(); return `${d.getFullYear()}${String(d.getMonth()+1).padStart(2,'0')}${String(d.getDate()).padStart(2,'0')}`; } function uid(){ return 'id_'+Math.random().toString(36).slice(2,10)+Date.now().toString(36); } function escapeHtml(s){ return (s||'').replace(/[&<>"']/g, c=>({ '&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function debounce(fn,ms){ let t; return (...a)=>{ clearTimeout(t); t=setTimeout(()=>fn(...a),ms); }; } function alertI18n(key){ alert(t(key)); }// -------------- Lightweight self-test (doesn't change user data) -------------- // Ensures integrity math and formatter work. Runs on isolated data. (function selfTest(){ try{ // pure compute with synthetic tx const init = {cash: 1000, biz: 0, personal: 0}; const tempTx = [ {type:'in', account:'cash', amount: 2000, date:'2025-01-10'}, {type:'out', account:'cash', amount: 500, date:'2025-01-11'}, {type:'in', account:'biz', amount: 300, date:'2025-01-12'}, ]; // compute let c = {...init}; for(const r of tempTx){ c[r.account] += (r.type==='in'?1:-1)*r.amount; } if(c.cash!==2500 || c.biz!==300 || c.personal!==0) throw new Error('balance mismatch'); // parse/format if(parseAmt('1,234,567 VND')!==1234567) throw new Error('parseAmt failed'); formatVND(123); // should not throw // ok }catch(e){ console.warn('Self-test failed', e); } })();})();