diff --git a/Entity/BudgetArchive.cs b/Entity/BudgetArchive.cs
index b8fd7df..3fc2bdf 100644
--- a/Entity/BudgetArchive.cs
+++ b/Entity/BudgetArchive.cs
@@ -38,6 +38,11 @@ public class BudgetArchive : BaseEntity
public record BudgetArchiveContent
{
+ ///
+ /// 预算ID
+ ///
+ public long Id { get; set; }
+
///
/// 预算名称
///
diff --git a/Service/BudgetService.cs b/Service/BudgetService.cs
index ee4fd60..f068114 100644
--- a/Service/BudgetService.cs
+++ b/Service/BudgetService.cs
@@ -51,6 +51,7 @@ public class BudgetService(
var (start, end) = GetPeriodRange(DateTime.Now, BudgetPeriodType.Month, referenceDate);
return [.. archive.Content.Select(c => new BudgetResult
{
+ Id = c.Id,
Name = c.Name,
Type = c.Type,
Limit = c.Limit,
@@ -158,7 +159,6 @@ public class BudgetService(
return archive?.Summary;
}
-
private async Task CalculateCategoryStatsAsync(
List budgets,
BudgetCategory category,
@@ -340,6 +340,7 @@ public class BudgetService(
var content = budgets.Select(b => new BudgetArchiveContent
{
+ Id = b.Id,
Name = b.Name,
Type = b.Type,
Limit = b.Limit,
@@ -621,26 +622,72 @@ public class BudgetService(
decimal noLimitIncomeAtPeriod = 0; // 新增:不记额收入汇总
decimal noLimitExpenseAtPeriod = 0; // 新增:不记额支出汇总
- var incomeItems = new List<(string Name, decimal Limit, decimal Factor, decimal Total)>();
- var expenseItems = new List<(string Name, decimal Limit, decimal Factor, decimal Total)>();
+ var incomeItems = new List<(string Name, decimal Limit, decimal Factor, decimal Historical, decimal Total)>();
+ var expenseItems = new List<(string Name, decimal Limit, decimal Factor, decimal Historical, decimal Total)>();
var noLimitIncomeItems = new List<(string Name, decimal Amount)>(); // 新增
var noLimitExpenseItems = new List<(string Name, decimal Amount)>(); // 新增
+ // 如果是年度计算,先从归档中获取所有历史数据
+ Dictionary<(long Id, int Month), (decimal HistoricalLimit, BudgetCategory Category, string Name)> historicalData = new();
+
+ if (periodType == BudgetPeriodType.Year)
+ {
+ var yearArchives = await budgetArchiveRepository.GetArchivesByYearAsync(date.Year);
+
+ // 按预算ID和月份记录历史数据
+ foreach (var archive in yearArchives)
+ {
+ foreach (var content in archive.Content)
+ {
+ // 跳过存款类预算
+ if (content.Category == BudgetCategory.Savings) continue;
+
+ historicalData[(content.Id, archive.Month)] = (
+ content.Limit,
+ content.Category,
+ content.Name);
+ }
+ }
+ }
+
+ // 处理当前预算
+ var processedIds = new HashSet();
foreach (var b in allBudgets)
{
if (b.Category == BudgetCategory.Savings) continue;
- // 折算系数:根据当前请求的 periodType (Year 或 Month),将预算 b 的 Limit 折算过来
+ processedIds.Add(b.Id);
decimal factor = 1.0m;
+ decimal historicalAmount = 0m;
+ var historicalMonths = new List();
if (periodType == BudgetPeriodType.Year)
{
- factor = b.Type switch
+ if (b.Type == BudgetPeriodType.Month)
{
- BudgetPeriodType.Month => 12,
- BudgetPeriodType.Year => 1,
- _ => 0
- };
+ // 月度预算在年度计算时:历史归档 + 剩余月份预算
+ // 收集该预算的所有历史月份数据
+ foreach (var ((id, month), (limit, _, _)) in historicalData)
+ {
+ if (id == b.Id)
+ {
+ historicalAmount += limit;
+ historicalMonths.Add(month);
+ }
+ }
+
+ // 计算剩余月份数(当前月到12月)
+ var remainingMonths = 12 - date.Month + 1;
+ factor = remainingMonths;
+ }
+ else if (b.Type == BudgetPeriodType.Year)
+ {
+ factor = 1;
+ }
+ else
+ {
+ factor = 0;
+ }
}
else if (periodType == BudgetPeriodType.Month)
{
@@ -656,12 +703,11 @@ public class BudgetService(
factor = 0; // 其他周期暂不计算虚拟存款
}
- if (factor <= 0) continue;
+ if (factor <= 0 && historicalAmount <= 0) continue;
- // 新增:处理不记额预算
+ // 处理不记额预算
if (b.NoLimit)
{
- // 不记额预算:计算实际发生的金额
var actualAmount = await CalculateCurrentAmountAsync(b, date);
if (b.Category == BudgetCategory.Income)
{
@@ -676,17 +722,63 @@ public class BudgetService(
}
else
{
- // 普通预算:按限额计算
- var subtotal = b.Limit * factor;
+ // 普通预算:历史金额 + 当前预算折算
+ var subtotal = historicalAmount + b.Limit * factor;
+ var displayName = b.Name;
+
+ // 如果有历史月份,添加月份范围显示
+ if (historicalMonths.Count > 0)
+ {
+ historicalMonths.Sort();
+ var monthRange = historicalMonths.Count == 1
+ ? $"{historicalMonths[0]}月"
+ : $"{historicalMonths[0]}~{historicalMonths[^1]}月";
+ displayName = $"{b.Name} ({monthRange})";
+ }
+
if (b.Category == BudgetCategory.Income)
{
incomeLimitAtPeriod += subtotal;
- incomeItems.Add((b.Name, b.Limit, factor, subtotal));
+ incomeItems.Add((displayName, b.Limit, factor, historicalAmount, subtotal));
}
else if (b.Category == BudgetCategory.Expense)
{
expenseLimitAtPeriod += subtotal;
- expenseItems.Add((b.Name, b.Limit, factor, subtotal));
+ expenseItems.Add((displayName, b.Limit, factor, historicalAmount, subtotal));
+ }
+ }
+ }
+
+ // 处理已删除的预算(只在归档中存在,但当前预算列表中不存在的)
+ if (periodType == BudgetPeriodType.Year)
+ {
+ // 按预算ID分组
+ var deletedBudgets = historicalData
+ .Where(kvp => !processedIds.Contains(kvp.Key.Id))
+ .GroupBy(kvp => kvp.Key.Id);
+
+ foreach (var group in deletedBudgets)
+ {
+ var budgetId = group.Key;
+ var months = group.Select(g => g.Key.Month).OrderBy(m => m).ToList();
+ var totalLimit = group.Sum(g => g.Value.HistoricalLimit);
+ var (_, category, name) = group.First().Value;
+
+ var monthRange = months.Count == 1
+ ? $"{months[0]}月"
+ : $"{months[0]}~{months[^1]}月";
+ var displayName = $"{name} ({monthRange}, 已删除)";
+
+ // 这是一个已被删除的预算,但有历史数据
+ if (category == BudgetCategory.Income)
+ {
+ incomeLimitAtPeriod += totalLimit;
+ incomeItems.Add((displayName, 0, months.Count, totalLimit, totalLimit));
+ }
+ else if (category == BudgetCategory.Expense)
+ {
+ expenseLimitAtPeriod += totalLimit;
+ expenseItems.Add((displayName, 0, months.Count, totalLimit, totalLimit));
}
}
}
@@ -696,28 +788,62 @@ public class BudgetService(
if (incomeItems.Count == 0) description.Append("无收入预算
");
else
{
- description.Append("""
-
-
-
- | 名称 |
- 金额 |
- 折算 |
- 合计 |
-
-
-
- """);
- foreach (var item in incomeItems)
+ // 根据是否有历史数据决定表格列
+ var hasHistoricalData = incomeItems.Any(i => i.Historical > 0);
+
+ if (hasHistoricalData)
{
- description.Append($"""
-
- | {item.Name} |
- {item.Limit:N0} |
- {item.Factor:0.##} |
- {item.Total:N0} |
-
+ description.Append("""
+
+
+
+ | 名称 |
+ 当前预算 |
+ 剩余月数 |
+ 历史归档 |
+ 合计 |
+
+
+
""");
+ foreach (var item in incomeItems)
+ {
+ description.Append($"""
+
+ | {item.Name} |
+ {item.Limit:N0} |
+ {item.Factor:0.##} |
+ {item.Historical:N0} |
+ {item.Total:N0} |
+
+ """);
+ }
+ }
+ else
+ {
+ description.Append("""
+
+
+
+ | 名称 |
+ 金额 |
+ 折算 |
+ 合计 |
+
+
+
+ """);
+ foreach (var item in incomeItems)
+ {
+ description.Append($"""
+
+ | {item.Name} |
+ {item.Limit:N0} |
+ {item.Factor:0.##} |
+ {item.Total:N0} |
+
+ """);
+ }
}
description.Append("
");
}
@@ -753,28 +879,62 @@ public class BudgetService(
if (expenseItems.Count == 0) description.Append("无支出预算
");
else
{
- description.Append("""
-
-
-
- | 名称 |
- 金额 |
- 折算 |
- 合计 |
-
-
-
- """);
- foreach (var (Name, Limit, Factor, Total) in expenseItems)
+ // 根据是否有历史数据决定表格列
+ var hasHistoricalData = expenseItems.Any(i => i.Historical > 0);
+
+ if (hasHistoricalData)
{
- description.Append($"""
-
- | {Name} |
- {Limit:N0} |
- {Factor:0.##} |
- {Total:N0} |
-
+ description.Append("""
+
+
+
+ | 名称 |
+ 当前预算 |
+ 剩余月数 |
+ 历史归档 |
+ 合计 |
+
+
+
""");
+ foreach (var item in expenseItems)
+ {
+ description.Append($"""
+
+ | {item.Name} |
+ {item.Limit:N0} |
+ {item.Factor:0.##} |
+ {item.Historical:N0} |
+ {item.Total:N0} |
+
+ """);
+ }
+ }
+ else
+ {
+ description.Append("""
+
+
+
+ | 名称 |
+ 金额 |
+ 折算 |
+ 合计 |
+
+
+
+ """);
+ foreach (var item in expenseItems)
+ {
+ description.Append($"""
+
+ | {item.Name} |
+ {item.Limit:N0} |
+ {item.Factor:0.##} |
+ {item.Total:N0} |
+
+ """);
+ }
}
description.Append("
");
}