diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index 8f4a699..93fe44d 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -4,6 +4,7 @@ import { RouterLink, useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' import { useUiStore } from '@/stores/ui' import { accountApi } from '@/api/accountApi' +import { loanSchedule, cardInstallmentSchedule, currentYm } from '@/utils/repayment' import { reminders } from '@/native/reminders' import { ID_LOGIN_ENABLED } from '@/config/features' import { enterDemo } from '@/demo' @@ -27,6 +28,8 @@ const networth = ref({ totalAssets: 0, totalLiabilities: 0, netWorth: 0 }) const budgetTotal = ref(0) const spentTotal = ref(0) const entries = ref([]) +// 이번 달 상환 예정(대출·카드할부) — 현재일 기준, 가계부에 상환 기록 있으면 제외 +const repayDue = ref({ total: 0, count: 0, hadAny: false }) const loading = ref(false) const error = ref(null) @@ -143,17 +146,19 @@ async function load() { error.value = null try { // 예산은 유료 전용 — 무료 회원은 호출하지 않음(403/업그레이드 리다이렉트 방지) - const [sum, nw, status, list] = await Promise.all([ + const [sum, nw, status, list, ws] = await Promise.all([ accountApi.summary({ year, month }), accountApi.netWorth(), auth.isPremium ? accountApi.budgetStatus({ year, month }) : Promise.resolve([]), accountApi.list({ year, month }), + accountApi.wallets(), ]) summary.value = sum networth.value = nw budgetTotal.value = (status || []).reduce((s, x) => s + (x.monthlyBudget || 0), 0) spentTotal.value = (status || []).reduce((s, x) => s + (x.spent || 0), 0) entries.value = list || [] + await computeRepayDue(ws || [], list || []) // 오늘 기록 여부로 가계부 리마인더 보정(오늘 기록 있으면 오늘자 알림 제외) if (reminders.isNative()) { const t = new Date() @@ -167,6 +172,54 @@ async function load() { } } +// 이번 달 상환 예정 계산: 대출 월 상환금 + 카드 할부(이번 달 회차). +// '가계부에 상환 기록(REPAYMENT) 있으면 제외' — 해당 계좌는 이번 달 이미 납입한 것으로 봄. +async function computeRepayDue(ws, monthEntries) { + const nw = currentYm() + const nowLabel = `${nw.y}.${String(nw.m).padStart(2, '0')}` + const paid = new Set( + (monthEntries || []) + .filter((e) => e.type === 'REPAYMENT' && e.toWalletId) + .map((e) => e.toWalletId), + ) + let total = 0 + let count = 0 + let hadAny = false + + // 대출: 이번 달 상환액(원금+이자) + for (const w of ws.filter((x) => x.type === 'LOAN' && x.loanRate)) { + const { rows } = loanSchedule(w, nw) + const row = rows.find((r) => r.label === nowLabel) + if (!row || !row.total) continue + hadAny = true + if (paid.has(w.id)) continue + total += row.total + count += 1 + } + + // 카드 할부: 이번 달 청구 회차 합(무이자). 카드에 이번 달 상환 기록 있으면 제외. + const cards = ws.filter((x) => x.type === 'CARD') + if (cards.length) { + const lists = await Promise.all(cards.map((c) => accountApi.walletEntries(c.id).catch(() => []))) + cards.forEach((c, i) => { + let cardSum = 0 + for (const e of (lists[i] || []).filter((e) => e.type === 'EXPENSE' && Number(e.installmentMonths) >= 2)) { + const sch = cardInstallmentSchedule(e, nw) + if (!sch) continue + const row = sch.rows.find((r) => r.label === nowLabel) + if (row) cardSum += row.total + } + if (cardSum <= 0) return + hadAny = true + if (paid.has(c.id)) return + total += cardSum + count += 1 + }) + } + + repayDue.value = { total, count, hadAny } +} + // 로그인/로그아웃 전환 시 갱신 watch(() => auth.isAuthenticated, (v) => { if (v) load() @@ -176,6 +229,7 @@ watch(() => auth.isAuthenticated, (v) => { budgetTotal.value = 0 spentTotal.value = 0 entries.value = [] + repayDue.value = { total: 0, count: 0, hadAny: false } activeDay.value = 0 } }) @@ -274,6 +328,22 @@ onMounted(load) + +