Compare commits
1 Commits
31cb4b7198
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 1b53af525e |
@@ -132,23 +132,46 @@ const repayTargetLoanWallet = computed(() => {
|
|||||||
const w = wallets.value.find((x) => x.id === form.toWalletId)
|
const w = wallets.value.find((x) => x.id === form.toWalletId)
|
||||||
return w?.type === 'LOAN' && w.loanRate ? w : null
|
return w?.type === 'LOAN' && w.loanRate ? w : null
|
||||||
})
|
})
|
||||||
// 납입총액 입력 → 원금/이자 자동분리
|
// 납입금액 입력용 (원리금균등만 사용)
|
||||||
const loanPaymentAmount = ref(null)
|
const loanPaymentAmount = ref(null)
|
||||||
|
|
||||||
|
function monthlyInterestOf(loan) {
|
||||||
|
return Math.round(Math.abs(loan.balance || 0) * (Number(loan.loanRate) / 100 / 12))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 상환방식별 자동계산: 원금균등·만기일시는 납입금액 입력 불필요
|
||||||
|
const loanAutoResult = computed(() => {
|
||||||
|
const loan = repayTargetLoanWallet.value
|
||||||
|
if (!loan) return null
|
||||||
|
const interest = monthlyInterestOf(loan)
|
||||||
|
if (loan.loanMethod === 'BULLET') {
|
||||||
|
return { interest, principal: 0, total: interest, needsInput: false }
|
||||||
|
}
|
||||||
|
if (loan.loanMethod === 'EQUAL_PRINCIPAL' && loan.loanAmount && loan.loanMonths) {
|
||||||
|
const principal = Math.round(Number(loan.loanAmount) / loan.loanMonths)
|
||||||
|
return { interest, principal, total: principal + interest, needsInput: false }
|
||||||
|
}
|
||||||
|
return { interest: null, principal: null, total: null, needsInput: true }
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(loanAutoResult, (r) => {
|
||||||
|
if (r && !r.needsInput) {
|
||||||
|
form.interest = r.interest
|
||||||
|
form.principal = r.principal
|
||||||
|
loanPaymentAmount.value = r.total
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
function calcLoanRepayment(payment) {
|
function calcLoanRepayment(payment) {
|
||||||
const loan = repayTargetLoanWallet.value
|
const loan = repayTargetLoanWallet.value
|
||||||
if (!loan || !payment || payment <= 0) return
|
if (!loan || !payment || payment <= 0) return
|
||||||
const monthlyRate = Number(loan.loanRate) / 100 / 12
|
const interest = monthlyInterestOf(loan)
|
||||||
const remainingDebt = Math.abs(loan.balance || 0)
|
form.interest = Math.min(interest, payment)
|
||||||
const monthlyInterest = Math.round(remainingDebt * monthlyRate)
|
|
||||||
if (loan.loanMethod === 'BULLET') {
|
|
||||||
form.interest = payment
|
|
||||||
form.principal = 0
|
|
||||||
} else {
|
|
||||||
form.interest = Math.min(monthlyInterest, payment)
|
|
||||||
form.principal = Math.max(0, payment - form.interest)
|
form.principal = Math.max(0, payment - form.interest)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
watch(loanPaymentAmount, (val) => { if (val) calcLoanRepayment(Number(val)) })
|
watch(loanPaymentAmount, (val) => {
|
||||||
|
if (loanAutoResult.value?.needsInput && val) calcLoanRepayment(Number(val))
|
||||||
|
})
|
||||||
watch(() => form.toWalletId, () => {
|
watch(() => form.toWalletId, () => {
|
||||||
loanPaymentAmount.value = null
|
loanPaymentAmount.value = null
|
||||||
form.principal = null
|
form.principal = null
|
||||||
@@ -1162,15 +1185,26 @@ onMounted(async () => {
|
|||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<!-- 금리 설정된 대출 계좌: 납입금액 입력 → 원금/이자 자동계산 -->
|
<!-- 금리 설정된 대출 계좌: 방식별 자동계산 -->
|
||||||
<template v-if="repayTargetLoanWallet">
|
<template v-if="repayTargetLoanWallet">
|
||||||
<label>납입금액
|
<!-- 원리금균등: 납입금액 직접 입력 -->
|
||||||
|
<template v-if="loanAutoResult?.needsInput">
|
||||||
|
<label>납입금액(원리금균등)
|
||||||
<input v-model.number="loanPaymentAmount" type="number" min="0"
|
<input v-model.number="loanPaymentAmount" type="number" min="0"
|
||||||
placeholder="이번 달 납입할 총금액" :disabled="submitting" @input="calcLoanRepayment(loanPaymentAmount)" />
|
placeholder="이번 달 납입할 총금액" :disabled="submitting" />
|
||||||
</label>
|
</label>
|
||||||
<div v-if="loanPaymentAmount > 0" class="loan-breakdown">
|
</template>
|
||||||
|
<!-- 원금균등·만기일시: 자동계산 결과 표시 -->
|
||||||
|
<template v-else>
|
||||||
|
<div class="loan-breakdown">
|
||||||
|
<span class="bd-label">{{ repayTargetLoanWallet.loanMethod === 'BULLET' ? '만기일시' : '원금균등' }} 자동계산</span>
|
||||||
|
<span>납입 <b>{{ (loanAutoResult?.total || 0).toLocaleString('ko-KR') }}원</b></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<!-- 계산 결과 표시 + 수동 조정 -->
|
||||||
|
<div v-if="(form.interest ?? null) !== null || (form.principal ?? null) !== null" class="loan-breakdown">
|
||||||
<span>이자 <b>{{ (form.interest || 0).toLocaleString('ko-KR') }}원</b></span>
|
<span>이자 <b>{{ (form.interest || 0).toLocaleString('ko-KR') }}원</b></span>
|
||||||
<span>원금상환 <b>{{ (form.principal || 0).toLocaleString('ko-KR') }}원</b></span>
|
<span>원금 <b>{{ (form.principal || 0).toLocaleString('ko-KR') }}원</b></span>
|
||||||
</div>
|
</div>
|
||||||
<label>이자(조정)<input v-model.number="form.interest" type="number" min="0" :disabled="submitting" /></label>
|
<label>이자(조정)<input v-model.number="form.interest" type="number" min="0" :disabled="submitting" /></label>
|
||||||
<label>원금(조정)<input v-model.number="form.principal" type="number" min="0" :disabled="submitting" /></label>
|
<label>원금(조정)<input v-model.number="form.principal" type="number" min="0" :disabled="submitting" /></label>
|
||||||
@@ -2027,6 +2061,12 @@ button.primary {
|
|||||||
.loan-breakdown b {
|
.loan-breakdown b {
|
||||||
color: hsla(160, 100%, 37%, 1);
|
color: hsla(160, 100%, 37%, 1);
|
||||||
}
|
}
|
||||||
|
.loan-breakdown .bd-label {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-bottom: 0.1rem;
|
||||||
|
}
|
||||||
.tag-field {
|
.tag-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const form = reactive({
|
|||||||
openingDate: '',
|
openingDate: '',
|
||||||
currentValue: null,
|
currentValue: null,
|
||||||
// 대출(LOAN) 전용
|
// 대출(LOAN) 전용
|
||||||
|
loanAmount: null,
|
||||||
loanRate: null,
|
loanRate: null,
|
||||||
loanMethod: '',
|
loanMethod: '',
|
||||||
loanMonths: null,
|
loanMonths: null,
|
||||||
@@ -201,7 +202,7 @@ function issuerLabel(t) {
|
|||||||
return t === 'BANK' ? '은행명' : t === 'CARD' ? '카드사' : t === 'INVEST' ? '증권사' : '대출기관'
|
return t === 'BANK' ? '은행명' : t === 'CARD' ? '카드사' : t === 'INVEST' ? '증권사' : '대출기관'
|
||||||
}
|
}
|
||||||
function openingLabel(t) {
|
function openingLabel(t) {
|
||||||
return t === 'BANK' || t === 'CASH' ? '초기 잔액' : t === 'CARD' ? '초기 미결제 금액' : t === 'INVEST' ? '투자금(투입원금)' : '대출 잔액(원금)'
|
return t === 'BANK' || t === 'CASH' ? '초기 잔액' : t === 'CARD' ? '초기 미결제 금액' : t === 'INVEST' ? '투자금(투입원금)' : '기록 시작 시 잔액'
|
||||||
}
|
}
|
||||||
// 투자 수익률(%) — 투입원금 대비 평가손익
|
// 투자 수익률(%) — 투입원금 대비 평가손익
|
||||||
function returnPct(w) {
|
function returnPct(w) {
|
||||||
@@ -234,6 +235,7 @@ function openCreate() {
|
|||||||
openingBalance: 0,
|
openingBalance: 0,
|
||||||
openingDate: '',
|
openingDate: '',
|
||||||
currentValue: null,
|
currentValue: null,
|
||||||
|
loanAmount: null,
|
||||||
loanRate: null,
|
loanRate: null,
|
||||||
loanMethod: '',
|
loanMethod: '',
|
||||||
loanMonths: null,
|
loanMonths: null,
|
||||||
@@ -254,6 +256,7 @@ function openEdit(w) {
|
|||||||
openingBalance: isLiability(w.type) ? -(w.openingBalance || 0) : w.openingBalance || 0,
|
openingBalance: isLiability(w.type) ? -(w.openingBalance || 0) : w.openingBalance || 0,
|
||||||
openingDate: w.openingDate || '',
|
openingDate: w.openingDate || '',
|
||||||
currentValue: w.currentValue ?? null,
|
currentValue: w.currentValue ?? null,
|
||||||
|
loanAmount: w.loanAmount ?? null,
|
||||||
loanRate: w.loanRate ?? null,
|
loanRate: w.loanRate ?? null,
|
||||||
loanMethod: w.loanMethod || '',
|
loanMethod: w.loanMethod || '',
|
||||||
loanMonths: w.loanMonths ?? null,
|
loanMonths: w.loanMonths ?? null,
|
||||||
@@ -285,6 +288,7 @@ async function submit() {
|
|||||||
form.type === 'INVEST' && form.currentValue !== '' && form.currentValue != null
|
form.type === 'INVEST' && form.currentValue !== '' && form.currentValue != null
|
||||||
? Number(form.currentValue)
|
? Number(form.currentValue)
|
||||||
: null,
|
: null,
|
||||||
|
loanAmount: isLoan && form.loanAmount ? Number(form.loanAmount) : null,
|
||||||
loanRate: isLoan && form.loanRate !== null && form.loanRate !== '' ? Number(form.loanRate) : null,
|
loanRate: isLoan && form.loanRate !== null && form.loanRate !== '' ? Number(form.loanRate) : null,
|
||||||
loanMethod: isLoan && form.loanMethod ? form.loanMethod : null,
|
loanMethod: isLoan && form.loanMethod ? form.loanMethod : null,
|
||||||
loanMonths: isLoan && form.loanMonths ? Number(form.loanMonths) : null,
|
loanMonths: isLoan && form.loanMonths ? Number(form.loanMonths) : null,
|
||||||
@@ -383,8 +387,9 @@ onBeforeUnmount(() => sortable?.destroy())
|
|||||||
>{{ revealedAccts.has(w.id) ? '🙈' : '👁' }}</button>
|
>{{ revealedAccts.has(w.id) ? '🙈' : '👁' }}</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="w.type === 'INVEST'"> · 투자금 {{ won(w.investedAmount) }}</template>
|
<template v-if="w.type === 'INVEST'"> · 투자금 {{ won(w.investedAmount) }}</template>
|
||||||
<template v-if="w.type === 'LOAN' && w.loanRate">
|
<template v-if="w.type === 'LOAN'">
|
||||||
· {{ w.loanRate }}% <span v-if="w.loanMethod">/ {{ loanMethodLabel(w.loanMethod) }}</span>
|
<span v-if="w.loanAmount"> · 실행 {{ won(w.loanAmount) }}원</span>
|
||||||
|
<span v-if="w.loanRate"> · {{ w.loanRate }}%<span v-if="w.loanMethod"> / {{ loanMethodLabel(w.loanMethod) }}</span></span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -484,6 +489,10 @@ onBeforeUnmount(() => sortable?.destroy())
|
|||||||
|
|
||||||
<!-- 대출 전용 -->
|
<!-- 대출 전용 -->
|
||||||
<template v-if="form.type === 'LOAN'">
|
<template v-if="form.type === 'LOAN'">
|
||||||
|
<label>대출 실행 금액(원금)
|
||||||
|
<input v-model.number="form.loanAmount" type="number" min="0"
|
||||||
|
placeholder="처음 빌린 금액 (예: 10000000)" :disabled="submitting" />
|
||||||
|
</label>
|
||||||
<label>연이자율(%)
|
<label>연이자율(%)
|
||||||
<input v-model.number="form.loanRate" type="number" min="0" max="100" step="0.01"
|
<input v-model.number="form.loanRate" type="number" min="0" max="100" step="0.01"
|
||||||
placeholder="예: 5.25" :disabled="submitting" />
|
placeholder="예: 5.25" :disabled="submitting" />
|
||||||
|
|||||||
Reference in New Issue
Block a user