gpt51

가계부 · Wealth Ledger (VND)
Sổ ghi chép tài sản
Sổ tài khoản hộ gia đình thời gian thực VND
Nhập liệu nhanh Tính toàn vẹn dữ liệu bình thường
VND
Tổng tài sản
₫0
Tiết kiệm trong tháng này: ₫0
tiền mặt
₫0
tài khoản doanh nghiệp
₫0
tài khoản cá nhân
₫0

Kế hoạch chi phí

Kế hoạch chi tiêu hàng tuần
₫0 / ₫0
Kế hoạch chi tiêu hàng tháng
₫0 / ₫0
VND
VND
Hãy thử tăng khoản tiết kiệm của bạn bằng cách giảm chi tiêu thực tế so với kế hoạch ban đầu.

Số dư ban đầu

VND
VND
VND
Số dư ban đầu được phản ánh ngay lập tức trong tổng số dư lũy kế.
Không thể khôi phục lại. Chúng tôi khuyên bạn nên sao lưu dữ liệu.

Thu nhập/Chi phí trong 12 tháng qua

Tiết kiệm hàng tháng

Lịch sử giao dịch

ngàytài khoảnloạiLoạibản ghi nhớsố lượngcông việc
0 trường hợp
nhập khẩu: ₫0
chi tiêu: ₫0
tiết kiệm: ₫0
Thói quen tạo nên sự giàu có. Hãy ghi chép lại thói quen của bạn ngay hôm nay nhé ✨
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); } })();})();