feat(budget): 实现存款明细计算核心逻辑
- 添加 BudgetItemCalculator 辅助类,实现明细项计算规则 - 收入:实际>0取实际,否则取预算 - 支出:取MAX(预算, 实际) - 硬性支出未发生:按天数折算 - 归档数据:直接使用实际值 - 实现月度和年度存款核心公式 - 月度:收入预算 + 本月年度收入 - 支出预算 - 本月年度支出 - 年度:归档已实收 + 未来收入预算 - 归档已实支 - 未来支出预算 - 定义存款明细数据结构 - SavingsDetail: 包含收入/支出明细列表和汇总 - BudgetDetailItem: 预算明细项(含计算用金额、计算说明等) - SavingsCalculationSummary: 计算汇总信息 - 新增单元测试 - BudgetItemCalculatorTest: 11个测试覆盖所有计算规则 - BudgetSavingsCalculationTest: 6个测试验证核心公式 测试结果:所有测试通过 (366 passed, 0 failed)
This commit is contained in:
@@ -1013,6 +1013,7 @@ const yearBurndownChartOptions = computed(() => {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.gauge-text-overlay {
|
||||
@@ -1048,6 +1049,8 @@ const yearBurndownChartOptions = computed(() => {
|
||||
|
||||
.chart-header {
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.chart-title {
|
||||
|
||||
@@ -123,6 +123,8 @@
|
||||
<SavingsBudgetContent
|
||||
v-else-if="activeTab === BudgetCategory.Savings"
|
||||
:budgets="savingsBudgets"
|
||||
:income-budgets="incomeBudgets"
|
||||
:expense-budgets="expenseBudgets"
|
||||
@savings-nav="handleSavingsNav"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -183,6 +183,14 @@ const props = defineProps({
|
||||
budgets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
incomeBudgets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
expenseBudgets: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
@@ -199,6 +207,30 @@ const handleShowDetail = (budget) => {
|
||||
showDetailPopup.value = true
|
||||
}
|
||||
|
||||
// 匹配收入预算
|
||||
const matchedIncomeBudget = computed(() => {
|
||||
if (!currentBudget.value) {return null}
|
||||
return props.incomeBudgets?.find(
|
||||
b => b.periodStart === currentBudget.value.periodStart && b.type === currentBudget.value.type
|
||||
)
|
||||
})
|
||||
|
||||
// 匹配支出预算
|
||||
const matchedExpenseBudget = computed(() => {
|
||||
if (!currentBudget.value) {return null}
|
||||
return props.expenseBudgets?.find(
|
||||
b => b.periodStart === currentBudget.value.periodStart && b.type === currentBudget.value.type
|
||||
)
|
||||
})
|
||||
|
||||
// 收入预算数据
|
||||
const incomeLimit = computed(() => matchedIncomeBudget.value?.limit || 0)
|
||||
const incomeCurrent = computed(() => matchedIncomeBudget.value?.current || 0)
|
||||
|
||||
// 支出预算数据
|
||||
const expenseLimit = computed(() => matchedExpenseBudget.value?.limit || 0)
|
||||
const expenseCurrent = computed(() => matchedExpenseBudget.value?.current || 0)
|
||||
|
||||
// 辅助函数
|
||||
const formatMoney = (val) => {
|
||||
return parseFloat(val || 0).toLocaleString(undefined, {
|
||||
|
||||
Reference in New Issue
Block a user