feat: 添加分类统计功能,支持获取月度和年度预算统计信息
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s

This commit is contained in:
孙诚
2026-01-09 15:42:59 +08:00
parent b41121400d
commit 2244d08b43
9 changed files with 524 additions and 177 deletions

View File

@@ -7,6 +7,11 @@ public interface IBudgetService
Task<BudgetResult?> GetStatisticsAsync(long id, DateTime? referenceDate = null);
Task<string> ArchiveBudgetsAsync(int year, int month);
/// <summary>
/// 获取指定分类的统计信息(月度和年度)
/// </summary>
Task<BudgetCategoryStats> GetCategoryStatsAsync(BudgetCategory category, DateTime? referenceDate = null);
}
public class BudgetService(
@@ -64,6 +69,92 @@ public class BudgetService(
return BudgetResult.FromEntity(budget, currentAmount, referenceDate);
}
public async Task<BudgetCategoryStats> GetCategoryStatsAsync(BudgetCategory category, DateTime? referenceDate = null)
{
var budgets = (await budgetRepository.GetAllAsync()).ToList();
var refDate = referenceDate ?? DateTime.Now;
var result = new BudgetCategoryStats();
// 获取月度统计
result.Month = await CalculateCategoryStatsAsync(budgets, category, BudgetPeriodType.Month, refDate);
// 获取年度统计
result.Year = await CalculateCategoryStatsAsync(budgets, category, BudgetPeriodType.Year, refDate);
return result;
}
private async Task<BudgetStatsDto> CalculateCategoryStatsAsync(
List<BudgetRecord> budgets,
BudgetCategory category,
BudgetPeriodType statType,
DateTime referenceDate)
{
var result = new BudgetStatsDto
{
PeriodType = statType,
Rate = 0,
Current = 0,
Limit = 0,
Count = 0
};
// 获取当前分类下所有未停止的预算
var relevant = budgets
.Where(b => b.Category == category && !b.IsStopped)
.ToList();
if (relevant.Count == 0)
{
return result;
}
result.Count = relevant.Count;
decimal totalCurrent = 0;
decimal totalLimit = 0;
foreach (var budget in relevant)
{
// 限额折算
var itemLimit = budget.Limit;
if (statType == BudgetPeriodType.Month && budget.Type == BudgetPeriodType.Year)
{
// 月度视图下,年度预算不参与限额计算
itemLimit = 0;
}
else if (statType == BudgetPeriodType.Year && budget.Type == BudgetPeriodType.Month)
{
// 年度视图下,月度预算折算为年度
itemLimit = budget.Limit * 12;
}
totalLimit += itemLimit;
// 当前值累加
var currentAmount = await CalculateCurrentAmountAsync(budget, referenceDate);
if (budget.Type == statType)
{
totalCurrent += currentAmount;
}
else
{
// 如果周期不匹配
if (statType == BudgetPeriodType.Year && budget.Type == BudgetPeriodType.Month)
{
// 在年度视图下,月度预算计入其当前值(作为对年度目前的贡献)
totalCurrent += currentAmount;
}
// 月度视图下,年度预算的 current 不计入
}
}
result.Limit = totalLimit;
result.Current = totalCurrent;
result.Rate = totalLimit > 0 ? (totalCurrent / totalLimit) * 100 : 0;
return result;
}
public async Task<string> ArchiveBudgetsAsync(int year, int month)
{
var referenceDate = new DateTime(year, month, 1);
@@ -393,18 +484,22 @@ public class BudgetService(
Category = BudgetCategory.Savings,
Type = periodType,
Limit = incomeLimitAtPeriod - expenseLimitAtPeriod,
StartDate = periodType == BudgetPeriodType.Year ? new DateTime(date.Year, 1, 1) : new DateTime(date.Year, date.Month, 1),
StartDate = periodType == BudgetPeriodType.Year
? new DateTime(date.Year, 1, 1)
: new DateTime(date.Year, date.Month, 1),
SelectedCategories = savingsCategories
};
// 计算实际发生的 收入 - 支出
var incomeHelper = new BudgetRecord { Category = BudgetCategory.Income, Type = periodType, StartDate = virtualBudget.StartDate, SelectedCategories = savingsCategories };
var expenseHelper = new BudgetRecord { Category = BudgetCategory.Expense, Type = periodType, StartDate = virtualBudget.StartDate, SelectedCategories = savingsCategories };
var current = await CalculateCurrentAmountAsync(new BudgetRecord
{
Category = virtualBudget.Category,
Type = virtualBudget.Type,
SelectedCategories = virtualBudget.SelectedCategories,
StartDate = virtualBudget.StartDate,
}, date);
var actualIncome = await CalculateCurrentAmountAsync(incomeHelper, date);
var actualExpense = await CalculateCurrentAmountAsync(expenseHelper, date);
return BudgetResult.FromEntity(virtualBudget, actualIncome - actualExpense, date, description.ToString());
return BudgetResult.FromEntity(virtualBudget, current, date, description.ToString());
}
}
@@ -459,3 +554,49 @@ public record BudgetResult
};
}
}
/// <summary>
/// 预算统计结果 DTO
/// </summary>
public class BudgetStatsDto
{
/// <summary>
/// 统计周期类型Month/Year
/// </summary>
public BudgetPeriodType PeriodType { get; set; }
/// <summary>
/// 使用率百分比0-100
/// </summary>
public decimal Rate { get; set; }
/// <summary>
/// 实际金额
/// </summary>
public decimal Current { get; set; }
/// <summary>
/// 目标/限额金额
/// </summary>
public decimal Limit { get; set; }
/// <summary>
/// 预算项数量
/// </summary>
public int Count { get; set; }
}
/// <summary>
/// 分类统计结果
/// </summary>
public class BudgetCategoryStats
{
/// <summary>
/// 月度统计
/// </summary>
public BudgetStatsDto Month { get; set; } = new();
/// <summary>
/// 年度统计
/// </summary>
public BudgetStatsDto Year { get; set; } = new();
}