- backup.js importBackup: .xlsx 파싱 → 이름 기준 payload → POST /account/restore - SettingsView: '가져오기' 파일 선택 + 파괴적 작업 확인(이중) + 완료 후 새로고침 - accountApi.restore (데모에선 차단) - 창 크기: 가로/세로 각각 선택(세로 700~1200), 한 축 변경 시 나머지 유지 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -82,3 +82,70 @@ export async function exportBackup() {
|
||||
|
||||
await saveWorkbook(XLSX, wb, fileName())
|
||||
}
|
||||
|
||||
// ===== 가져오기(복구) — 엑셀 파싱 → 백엔드 복구 =====
|
||||
export async function importBackup(file) {
|
||||
const XLSX = await import('xlsx')
|
||||
const buf = await file.arrayBuffer()
|
||||
const wb = XLSX.read(buf, { type: 'array' })
|
||||
|
||||
const rows = (name) => {
|
||||
const ws = wb.Sheets[name]
|
||||
return ws ? XLSX.utils.sheet_to_json(ws, { defval: null }) : []
|
||||
}
|
||||
const str = (v) => (v === null || v === undefined || v === '' ? null : String(v).trim())
|
||||
const num = (v) => (v === null || v === undefined || v === '' ? null : Number(v))
|
||||
const bool = (v) => v === true || v === 1 || ['true', 'TRUE', '1', 'Y', '예'].includes(String(v))
|
||||
const date = (v) => {
|
||||
if (v === null || v === undefined || v === '') return null
|
||||
if (typeof v === 'number' && XLSX.SSF) {
|
||||
const d = XLSX.SSF.parse_date_code(v)
|
||||
if (d) return `${d.y}-${String(d.m).padStart(2, '0')}-${String(d.d).padStart(2, '0')}`
|
||||
}
|
||||
return String(v).trim().slice(0, 10) // 'YYYY-MM-DD'
|
||||
}
|
||||
|
||||
const payload = {
|
||||
wallets: rows('계좌')
|
||||
.map((r) => ({
|
||||
type: str(r['종류']), name: str(r['이름']), issuer: str(r['은행/카드사']),
|
||||
accountNumber: str(r['계좌번호']), cardType: str(r['카드유형']),
|
||||
openingBalance: num(r['개시잔액']), openingDate: date(r['개시일']), currentValue: num(r['평가액(수동)']),
|
||||
}))
|
||||
.filter((w) => w.name && w.type),
|
||||
categories: rows('분류')
|
||||
.map((r) => ({ type: str(r['구분']), name: str(r['이름']), parent: str(r['대분류']) }))
|
||||
.filter((c) => c.name && c.type),
|
||||
tags: rows('태그').map((r) => str(r['이름'])).filter(Boolean),
|
||||
entries: rows('내역')
|
||||
.map((r) => ({
|
||||
entryDate: date(r['날짜']), type: str(r['구분']), category: str(r['분류']),
|
||||
amount: num(r['금액']), memo: str(r['메모']), wallet: str(r['계좌']), toWallet: str(r['입금계좌']),
|
||||
installmentMonths: num(r['할부개월']),
|
||||
tags: str(r['태그']) ? String(r['태그']).split(',').map((s) => s.trim()).filter(Boolean) : [],
|
||||
}))
|
||||
.filter((e) => e.entryDate && e.type),
|
||||
recurrings: rows('고정지출')
|
||||
.map((r) => ({
|
||||
title: str(r['제목']), type: str(r['구분']), amount: num(r['금액']), category: str(r['분류']), memo: str(r['메모']),
|
||||
wallet: str(r['계좌']), toWallet: str(r['입금계좌']), frequency: str(r['주기']),
|
||||
dayOfMonth: num(r['일']), dayOfWeek: num(r['요일']), monthOfYear: num(r['월']),
|
||||
startDate: date(r['시작일']), endDate: date(r['종료일']), active: bool(r['활성']),
|
||||
}))
|
||||
.filter((r) => r.title && r.type),
|
||||
budgets: rows('예산')
|
||||
.map((r) => ({
|
||||
category: str(r['분류']), fixed: bool(r['고정']), baseUnit: str(r['기준단위']), baseAmount: num(r['기준금액']),
|
||||
daily: num(r['일']), weekly: num(r['주']), monthly: num(r['월']), yearly: num(r['년']),
|
||||
}))
|
||||
.filter((b) => b.category),
|
||||
quickEntries: rows('자주쓰는내역')
|
||||
.map((r) => ({
|
||||
label: str(r['라벨']), type: str(r['구분']), category: str(r['분류']),
|
||||
amount: num(r['금액']), memo: str(r['메모']), wallet: str(r['계좌']),
|
||||
}))
|
||||
.filter((q) => q.type),
|
||||
}
|
||||
|
||||
await accountApi.restore(payload)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user