feat(#35): rendimento CDI automático em contas de investimento
- Migration 010: colunas yield_type e last_yield_date em accounts + source='yield' nas transações
- CDIYieldService: busca taxas BCB (serie 12) e cria transação source=yield com rendimento acumulado
- Weekends/feriados sem taxa usam última taxa disponível (AC4)
- GET /api/accounts dispara cálculo CDI on-demand antes de retornar saldos
- AccountsView: seletor yield_type (none/cdi/variable) + badge CDI/VAR na listagem
- Transações source=yield aparecem no histórico com descrição 'Rendimento CDI — {período}'
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
@@ -8,6 +8,8 @@ export interface Account {
|
||||
type: 'checking' | 'savings' | 'investment' | 'credit'
|
||||
initial_balance: number
|
||||
balance: number
|
||||
yield_type: 'none' | 'cdi' | 'variable'
|
||||
last_yield_date?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -16,6 +18,7 @@ export interface AccountInput {
|
||||
name: string
|
||||
type: string
|
||||
initial_balance: number
|
||||
yield_type: string
|
||||
}
|
||||
|
||||
export const useAccountsStore = defineStore('accounts', () => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const typeLabels: Record<string, string> = {
|
||||
credit: 'Cartão de Crédito',
|
||||
}
|
||||
|
||||
const blank = (): AccountInput => ({ name: '', type: 'checking', initial_balance: 0 })
|
||||
const blank = (): AccountInput => ({ name: '', type: 'checking', initial_balance: 0, yield_type: 'none' })
|
||||
const form = ref(blank())
|
||||
const editId = ref<number | null>(null)
|
||||
const initialRaw = ref('')
|
||||
@@ -27,7 +27,7 @@ function startEdit(id: number) {
|
||||
const a = store.accounts.find((x) => x.id === id)
|
||||
if (!a) return
|
||||
editId.value = id
|
||||
form.value = { name: a.name, type: a.type, initial_balance: a.initial_balance }
|
||||
form.value = { name: a.name, type: a.type, initial_balance: a.initial_balance, yield_type: a.yield_type ?? 'none' }
|
||||
initialRaw.value = a.initial_balance.toLocaleString('pt-BR', { minimumFractionDigits: 2 })
|
||||
formError.value = null
|
||||
}
|
||||
@@ -85,6 +85,11 @@ function totalBalance() {
|
||||
<option value="investment">Investimento</option>
|
||||
<option value="credit">Cartão de Crédito</option>
|
||||
</select>
|
||||
<select v-model="form.yield_type" class="fc-select fc-acc-form__yield">
|
||||
<option value="none">Sem rendimento</option>
|
||||
<option value="cdi">CDI automático</option>
|
||||
<option value="variable">Renda variável</option>
|
||||
</select>
|
||||
<input
|
||||
v-model="initialRaw"
|
||||
placeholder="Saldo inicial (ex: 1.500,00)"
|
||||
@@ -120,7 +125,11 @@ function totalBalance() {
|
||||
:class="{ 'fc-acc-item--editing': editId === a.id }"
|
||||
>
|
||||
<div class="fc-acc-item__info">
|
||||
<span class="fc-body fc-acc-item__name">{{ a.name }}</span>
|
||||
<div class="fc-acc-item__name-row">
|
||||
<span class="fc-body fc-acc-item__name">{{ a.name }}</span>
|
||||
<span v-if="a.yield_type === 'cdi'" class="fc-acc-badge fc-acc-badge--cdi fc-pixel">CDI</span>
|
||||
<span v-else-if="a.yield_type === 'variable'" class="fc-acc-badge fc-acc-badge--var fc-pixel">VAR</span>
|
||||
</div>
|
||||
<span class="fc-mono fc-acc-item__meta">
|
||||
{{ typeLabels[a.type] }} · inicial {{ fmt(a.initial_balance) }}
|
||||
</span>
|
||||
@@ -165,8 +174,9 @@ function totalBalance() {
|
||||
}
|
||||
|
||||
.fc-acc-form__name { flex: 1; min-width: 160px; }
|
||||
.fc-acc-form__type { width: 160px; }
|
||||
.fc-acc-form__balance { width: 160px; }
|
||||
.fc-acc-form__type { width: 150px; }
|
||||
.fc-acc-form__yield { width: 160px; }
|
||||
.fc-acc-form__balance { width: 150px; }
|
||||
|
||||
.fc-acc-form__error {
|
||||
color: var(--fc-red);
|
||||
@@ -237,9 +247,16 @@ function totalBalance() {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.fc-acc-item__name-row { display: flex; align-items: center; gap: 8px; }
|
||||
.fc-acc-item__name { font-size: 14px; font-weight: 500; }
|
||||
.fc-acc-item__meta { font-size: 11px; color: var(--fc-text-dim); }
|
||||
|
||||
.fc-acc-badge {
|
||||
font-size: 7px; padding: 2px 5px; border-radius: 2px; letter-spacing: .05em;
|
||||
}
|
||||
.fc-acc-badge--cdi { background: rgba(34,197,94,.2); color: var(--fc-green); border: 1px solid var(--fc-green); }
|
||||
.fc-acc-badge--var { background: rgba(251,191,36,.2); color: var(--fc-gold); border: 1px solid var(--fc-gold); }
|
||||
|
||||
.fc-acc-item__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user