feat(budget): 添加存款明细数据生成逻辑
- 实现 GenerateMonthlyDetails 方法生成月度存款明细 - 为每个预算项调用 BudgetItemCalculator 计算有效金额 - 生成计算说明(使用预算/使用实际/超支/按天折算) - 标记超支项目 - 生成汇总信息(总收入、总支出、计划存款) - GetForMonthAsync 现在返回 Details 字段 - 包含收入明细列表 - 包含支出明细列表 - 包含计算汇总和公式 - 新增集成测试验证 Details 字段生成正确 - 验证收入项计算规则 - 验证支出项超支标记 - 验证硬性支出处理 - 验证汇总计算 测试结果:58个预算测试全部通过
This commit is contained in:
@@ -58,9 +58,96 @@ public class BudgetSavingsTest : BaseTest
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Limit.Should().Be(10000 - 2000); // 计划收入 - 计划支出 = 10000 - 2000 = 8000
|
||||
result.Limit.Should().Be(8000);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSavings_月度_应返回Details字段()
|
||||
{
|
||||
// Arrange
|
||||
var referenceDate = new DateTime(2024, 2, 15);
|
||||
_dateTimeProvider.Now.Returns(referenceDate);
|
||||
|
||||
var budgets = new List<BudgetRecord>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1, Name = "工资", Type = BudgetPeriodType.Month, Limit = 10000, Category = BudgetCategory.Income,
|
||||
SelectedCategories = "工资"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 2, Name = "餐饮", Type = BudgetPeriodType.Month, Limit = 2000, Category = BudgetCategory.Expense,
|
||||
SelectedCategories = "餐饮"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 3, Name = "房租", Type = BudgetPeriodType.Month, Limit = 3000, Category = BudgetCategory.Expense,
|
||||
SelectedCategories = "房租", IsMandatoryExpense = true
|
||||
}
|
||||
};
|
||||
|
||||
var transactions = new Dictionary<(string, TransactionType), decimal>
|
||||
{
|
||||
{ ("工资", TransactionType.Income), 10000m },
|
||||
{ ("餐饮", TransactionType.Expense), 2500m }, // 超支
|
||||
{ ("房租", TransactionType.Expense), 0m } // 硬性未发生
|
||||
};
|
||||
|
||||
_transactionStatisticsService.GetAmountGroupByClassifyAsync(
|
||||
Arg.Any<DateTime>(),
|
||||
Arg.Any<DateTime>()
|
||||
).Returns(transactions);
|
||||
|
||||
_configService.GetConfigByKeyAsync<string>("SavingsCategories").Returns("存款");
|
||||
|
||||
// Act
|
||||
var result = await _service.GetSavingsDtoAsync(BudgetPeriodType.Month, referenceDate, budgets);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Details.Should().NotBeNull();
|
||||
|
||||
// 验证收入明细
|
||||
result.Details!.IncomeItems.Should().HaveCount(1);
|
||||
var incomeItem = result.Details.IncomeItems[0];
|
||||
incomeItem.Name.Should().Be("工资");
|
||||
incomeItem.BudgetLimit.Should().Be(10000);
|
||||
incomeItem.ActualAmount.Should().Be(10000);
|
||||
incomeItem.EffectiveAmount.Should().Be(10000);
|
||||
incomeItem.CalculationNote.Should().Be("使用实际");
|
||||
|
||||
// 验证支出明细
|
||||
result.Details.ExpenseItems.Should().HaveCount(2);
|
||||
|
||||
// 餐饮超支
|
||||
var expenseItem1 = result.Details.ExpenseItems.FirstOrDefault(e => e.Name == "餐饮");
|
||||
expenseItem1.Should().NotBeNull();
|
||||
expenseItem1!.BudgetLimit.Should().Be(2000);
|
||||
expenseItem1.ActualAmount.Should().Be(2500);
|
||||
expenseItem1.EffectiveAmount.Should().Be(2500); // MAX(2000, 2500)
|
||||
expenseItem1.CalculationNote.Should().Be("使用实际(超支)");
|
||||
expenseItem1.IsOverBudget.Should().BeTrue();
|
||||
|
||||
// 房租按天折算(硬性消费在实际为0时会自动填充)
|
||||
var expenseItem2 = result.Details.ExpenseItems.FirstOrDefault(e => e.Name == "房租");
|
||||
expenseItem2.Should().NotBeNull();
|
||||
expenseItem2!.BudgetLimit.Should().Be(3000);
|
||||
// 硬性消费在 GetForMonthAsync 中已经填充了按天折算的值到 current
|
||||
expenseItem2.ActualAmount.Should().BeApproximately(3000m / 29 * 15, 0.01m);
|
||||
// EffectiveAmount 使用 MAX(预算3000, 实际1551.72) = 3000
|
||||
expenseItem2.EffectiveAmount.Should().Be(3000);
|
||||
expenseItem2.CalculationNote.Should().Be("使用预算"); // MAX 后选择了预算值
|
||||
|
||||
// 验证汇总
|
||||
result.Details.Summary.Should().NotBeNull();
|
||||
result.Details.Summary.TotalIncomeBudget.Should().BeApproximately(10000, 0.01m);
|
||||
// 支出汇总:餐饮2500 + 房租3000(MAX) = 5500
|
||||
result.Details.Summary.TotalExpenseBudget.Should().BeApproximately(5500, 1m);
|
||||
result.Details.Summary.PlannedSavings.Should().BeApproximately(4500, 1m);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task GetSavings_月度_年度收支_Test()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user