feat(repayment): 대출 월 상환금 입력 시 원금·이자 은행과 정확 일치
Deploy / deploy (push) Failing after 11m59s

- 계좌 관리 대출 폼에 '월 상환금(선택)' 입력(원리금균등)
- repayment.js: loanPayment 있으면 이자=잔액×이율, 원금=상환금-이자로 상각
  → 남은 개월수 추정 오차(±1회) 없이 은행 조회액과 일치. 미입력 시 기존 추정

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
sb
2026-07-07 23:08:00 +09:00
parent b039069d6c
commit 18f015f7fd
2 changed files with 30 additions and 0 deletions
+19
View File
@@ -71,6 +71,25 @@ export function loanSchedule(w, now = currentYm(), cap = 600) {
} }
// EQUAL_PAYMENT 원리금균등 // EQUAL_PAYMENT 원리금균등
// (0) 월 상환금(고정)이 입력돼 있으면 그대로 사용 → 이자=잔액×이율, 원금=상환금-이자 로 은행과 정확히 일치
const fixed = Number(w.loanPayment) || 0
if (fixed > 0) {
while (bal > 0 && rows.length < cap) {
const interest = Math.round(bal * r)
let principal = fixed - interest
if (principal <= 0) {
// 상환금이 이자에 못 미침(데이터 이상) → 더 진행 못함
rows.push({ label: label(y, m), principal: Math.max(0, principal), interest, total: fixed, balance: bal })
break
}
principal = Math.min(principal, bal)
bal = Math.max(0, bal - principal)
rows.push({ label: label(y, m), principal, interest, total: principal + interest, balance: bal })
;({ y, m } = addMonths(y, m, 1))
}
return { rows, partial: false }
}
// (1) 남은 개월수 n 산출: 최초 상환액으로 역산(loanStart 없어도 정확) → 안되면 loanStart 경과 // (1) 남은 개월수 n 산출: 최초 상환액으로 역산(loanStart 없어도 정확) → 안되면 loanStart 경과
let n = null let n = null
if (w.loanAmount && w.loanMonths && r > 0) { if (w.loanAmount && w.loanMonths && r > 0) {
+11
View File
@@ -62,6 +62,7 @@ const form = reactive({
loanMethod: '', loanMethod: '',
loanMonths: null, loanMonths: null,
loanStart: '', loanStart: '',
loanPayment: null,
}) })
const submitting = ref(false) const submitting = ref(false)
const formError = ref(null) const formError = ref(null)
@@ -187,6 +188,7 @@ function openCreate() {
loanMethod: '', loanMethod: '',
loanMonths: null, loanMonths: null,
loanStart: '', loanStart: '',
loanPayment: null,
}) })
formError.value = null formError.value = null
formOpen.value = true formOpen.value = true
@@ -208,6 +210,7 @@ function openEdit(w) {
loanMethod: w.loanMethod || '', loanMethod: w.loanMethod || '',
loanMonths: w.loanMonths ?? null, loanMonths: w.loanMonths ?? null,
loanStart: w.loanStart || '', loanStart: w.loanStart || '',
loanPayment: w.loanPayment ?? null,
}) })
formError.value = null formError.value = null
formOpen.value = true formOpen.value = true
@@ -240,6 +243,7 @@ async function submit() {
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,
loanStart: isLoan && form.loanStart ? form.loanStart : null, loanStart: isLoan && form.loanStart ? form.loanStart : null,
loanPayment: isLoan && form.loanPayment ? Number(form.loanPayment) : null,
} }
try { try {
if (editId.value) await accountApi.updateWallet(editId.value, payload) if (editId.value) await accountApi.updateWallet(editId.value, payload)
@@ -413,6 +417,13 @@ onBeforeUnmount(destroySortables)
<label>대출시작일 <label>대출시작일
<input v-model="form.loanStart" type="date" :disabled="submitting" /> <input v-model="form.loanStart" type="date" :disabled="submitting" />
</label> </label>
<label v-if="form.loanMethod === 'EQUAL_PAYMENT' || form.loanMethod === ''"> 상환금(선택)
<input v-model.number="form.loanPayment" type="number" min="0"
placeholder="은행 조회 고정 월납입금 (예: 697868)" :disabled="submitting" />
</label>
<p v-if="form.loanMethod === 'EQUAL_PAYMENT' || form.loanMethod === ''" class="form-hint">
원리금균등은 <b> 상환금</b> 입력하면 상환표의 원금·이자가 은행 조회액과 정확히 일치합니다. (미입력 잔액·개월수로 추정)
</p>
</template> </template>
<p v-if="formError" class="msg error">{{ formError }}</p> <p v-if="formError" class="msg error">{{ formError }}</p>