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($""" - - - - - - + description.Append(""" +
名称金额折算合计
{item.Name}{item.Limit:N0}{item.Factor:0.##}{item.Total:N0}
+ + + + + + + + + + """); + foreach (var item in incomeItems) + { + description.Append($""" + + + + + + + + """); + } + } + else + { + description.Append(""" +
名称当前预算剩余月数历史归档合计
{item.Name}{item.Limit:N0}{item.Factor:0.##}{item.Historical:N0}{item.Total:N0}
+ + + + + + + + + + """); + foreach (var item in incomeItems) + { + description.Append($""" + + + + + + + """); + } } description.Append("
名称金额折算合计
{item.Name}{item.Limit:N0}{item.Factor:0.##}{item.Total:N0}
"); } @@ -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($""" - - - - - - + description.Append(""" +
名称金额折算合计
{Name}{Limit:N0}{Factor:0.##}{Total:N0}
+ + + + + + + + + + """); + foreach (var item in expenseItems) + { + description.Append($""" + + + + + + + + """); + } + } + else + { + description.Append(""" +
名称当前预算剩余月数历史归档合计
{item.Name}{item.Limit:N0}{item.Factor:0.##}{item.Historical:N0}{item.Total:N0}
+ + + + + + + + + + """); + foreach (var item in expenseItems) + { + description.Append($""" + + + + + + + """); + } } description.Append("
名称金额折算合计
{item.Name}{item.Limit:N0}{item.Factor:0.##}{item.Total:N0}
"); }