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
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:
@@ -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();
|
||||
}
|
||||
@@ -82,8 +82,6 @@ public class EmailHandleService(
|
||||
{
|
||||
logger.LogInformation("处理交易记录: 卡号 {Card}, 交易原因 {Reason}, 金额 {Amount}, 余额 {Balance}, 类型 {Type}", card, reason, amount, balance, type);
|
||||
|
||||
|
||||
|
||||
var record = await SaveTransactionRecordAsync(
|
||||
card,
|
||||
reason,
|
||||
@@ -104,7 +102,9 @@ public class EmailHandleService(
|
||||
records.Add(record);
|
||||
}
|
||||
|
||||
_ = await AnalyzeClassifyAsync(records.ToArray());
|
||||
// var analysisResult = await AnalyzeClassifyAsync(records.ToArray());
|
||||
// TODO 不应该直接保存 应该保存在备用字段上,前端确认后再更新到正式字段
|
||||
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class BudgetArchiveJob(
|
||||
logger.LogInformation("开始执行预算归档任务");
|
||||
|
||||
// 每个月1号执行,归档上个月的数据
|
||||
var targetDate = DateTime.Now.AddMonths(0); // TODO 调试期间使用
|
||||
var targetDate = DateTime.Now.AddMonths(-1);
|
||||
var year = targetDate.Year;
|
||||
var month = targetDate.Month;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user