feat: 상환/납부 연회비 입력(카드 대상 시) + 원금/이자 안내
CI / build (push) Failing after 15m31s

- 대상이 카드일 때만 연회비 입력란 노출(repayTargetIsCard)
- 원금=이체·이자/연회비=지출 안내 placeholder, 검증 '원금·이자·연회비 중 하나'

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-06-12 22:52:34 +09:00
parent 9f079fcead
commit 9aa5f5d690
+19 -10
View File
@@ -76,8 +76,13 @@ function resetFilters() {
// 추가/수정 모달 // 추가/수정 모달
const formOpen = ref(false) const formOpen = ref(false)
const editId = ref(null) const editId = ref(null)
const form = reactive({ entryDate: '', type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: '', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, installmentMonths: '' }) const form = reactive({ entryDate: '', type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: '', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, annualFee: null, installmentMonths: '' })
const isRepayment = computed(() => form.type === 'REPAYMENT') const isRepayment = computed(() => form.type === 'REPAYMENT')
// 상환 대상이 카드면 연회비 입력 노출(대출은 연회비 없음)
const repayTargetIsCard = computed(() => {
const w = wallets.value.find((x) => x.id === form.toWalletId)
return !!w && w.type === 'CARD'
})
// 카드 지출일 때만 할부 입력 노출 (2~24개월, 일시불은 빈값) // 카드 지출일 때만 할부 입력 노출 (2~24개월, 일시불은 빈값)
const showInstallment = computed(() => form.type === 'EXPENSE' && form.walletKind === 'CARD') const showInstallment = computed(() => form.type === 'EXPENSE' && form.walletKind === 'CARD')
const installmentMonthly = computed(() => { const installmentMonthly = computed(() => {
@@ -365,7 +370,7 @@ function todayStr() {
function openCreate() { function openCreate() {
editId.value = null editId.value = null
editingPending.value = false editingPending.value = false
Object.assign(form, { entryDate: todayStr(), type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: 'BANK', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, installmentMonths: '' }) Object.assign(form, { entryDate: todayStr(), type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: 'BANK', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, annualFee: null, installmentMonths: '' })
selectedTagIds.value = [] selectedTagIds.value = []
cancelAddCategory() cancelAddCategory()
formError.value = null formError.value = null
@@ -389,6 +394,7 @@ function openEdit(e) {
toWalletId: e.toWalletId || '', toWalletId: e.toWalletId || '',
principal: null, principal: null,
interest: null, interest: null,
annualFee: null,
installmentMonths: e.installmentMonths || '', installmentMonths: e.installmentMonths || '',
}) })
// 태그 이름 → id 매핑 (현재 태그 목록 기준) // 태그 이름 → id 매핑 (현재 태그 목록 기준)
@@ -406,7 +412,7 @@ async function submit() {
formError.value = '거래일을 입력하세요.' formError.value = '거래일을 입력하세요.'
return return
} }
// 상환/납부: 원금=이체, 이자=지출 자동 분리 // 상환/납부: 원금=이체, 이자·연회비=지출 자동 분리
if (isRepayment.value) { if (isRepayment.value) {
if (!form.walletId || !form.toWalletId) { if (!form.walletId || !form.toWalletId) {
formError.value = '출금 계좌와 대상(대출/카드)을 선택하세요.' formError.value = '출금 계좌와 대상(대출/카드)을 선택하세요.'
@@ -418,20 +424,22 @@ async function submit() {
} }
const principal = Number(form.principal) || 0 const principal = Number(form.principal) || 0
const interest = Number(form.interest) || 0 const interest = Number(form.interest) || 0
if (principal <= 0 && interest <= 0) { const annualFee = repayTargetIsCard.value ? (Number(form.annualFee) || 0) : 0
formError.value = '원금 또는 이자를 입력하세요.' if (principal <= 0 && interest <= 0 && annualFee <= 0) {
formError.value = '원금·이자·연회비 중 하나는 입력하세요.'
return return
} }
submitting.value = true submitting.value = true
try { try {
// 상환은 원금=이체 / 이자=지출 2건으로 저장된다. 수정 시에는 기존 1건을 // 상환은 원금=이체 / 이자·연회비=지출로 나눠 저장된다. 수정 시에는 기존 1건을
// 상환 2건으로 다시 만든다(상환 생성 성공 후 원본 삭제 — 중간 실패 시 원본 보존). // 상환 N건으로 다시 만든다(상환 생성 성공 후 원본 삭제 — 중간 실패 시 원본 보존).
await accountApi.repayment({ await accountApi.repayment({
entryDate: form.entryDate, entryDate: form.entryDate,
fromWalletId: form.walletId, fromWalletId: form.walletId,
targetWalletId: form.toWalletId, targetWalletId: form.toWalletId,
principal, principal,
interest, interest,
annualFee,
memo: form.memo || null, memo: form.memo || null,
}) })
if (editId.value) { if (editId.value) {
@@ -714,7 +722,7 @@ onMounted(async () => {
</div> </div>
</template> </template>
<!-- 상환/납부: 대상(대출/카드) + 원금 + 이자 --> <!-- 상환/납부: 대상(대출/카드) + 원금 + 이자 (+카드면 연회비). 원금=이체·이자/연회비=지출 -->
<template v-if="isRepayment"> <template v-if="isRepayment">
<label>대상(대출/카드) <label>대상(대출/카드)
<select v-model="form.toWalletId" :disabled="submitting"> <select v-model="form.toWalletId" :disabled="submitting">
@@ -724,8 +732,9 @@ onMounted(async () => {
</option> </option>
</select> </select>
</label> </label>
<label>원금<input v-model.number="form.principal" type="number" min="0" placeholder="원" :disabled="submitting" /></label> <label>원금<input v-model.number="form.principal" type="number" min="0" placeholder="원 (이체로 기록)" :disabled="submitting" /></label>
<label>이자<input v-model.number="form.interest" type="number" min="0" placeholder="원" :disabled="submitting" /></label> <label>이자<input v-model.number="form.interest" type="number" min="0" placeholder="원 (지출·분류 이자)" :disabled="submitting" /></label>
<label v-if="repayTargetIsCard">연회비<input v-model.number="form.annualFee" type="number" min="0" placeholder="원 (지출·분류 연회비)" :disabled="submitting" /></label>
</template> </template>
<label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류 <label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류