diff --git a/Entity/BudgetArchive.cs b/Entity/BudgetArchive.cs index d52ec14..af405ac 100644 --- a/Entity/BudgetArchive.cs +++ b/Entity/BudgetArchive.cs @@ -23,6 +23,16 @@ public class BudgetArchive : BaseEntity /// public DateTime ArchiveDate { get; set; } = DateTime.Now; + /// + /// 支出结余(预算 - 实际,正数表示省钱,负数表示超支) + /// + public decimal ExpenseSurplus { get; set; } + + /// + /// 收入结余(实际 - 预算,正数表示超额收入,负数表示未达预期) + /// + public decimal IncomeSurplus { get; set; } + public string? Summary { get; set; } } diff --git a/Repository/BudgetArchiveRepository.cs b/Repository/BudgetArchiveRepository.cs index 02ff05e..7dab7c5 100644 --- a/Repository/BudgetArchiveRepository.cs +++ b/Repository/BudgetArchiveRepository.cs @@ -5,6 +5,8 @@ public interface IBudgetArchiveRepository : IBaseRepository Task GetArchiveAsync(int year, int month); Task> GetListAsync(int year, int month); + + Task> GetArchivesByYearAsync(int year); } public class BudgetArchiveRepository( @@ -25,4 +27,12 @@ public class BudgetArchiveRepository( .Where(a => a.Year == year && a.Month == month) .ToListAsync(); } + + public async Task> GetArchivesByYearAsync(int year) + { + return await FreeSql.Select() + .Where(a => a.Year == year) + .OrderBy(a => a.Month) + .ToListAsync(); + } } \ No newline at end of file diff --git a/Service/BudgetService.cs b/Service/BudgetService.cs index d385aeb..c3ec652 100644 --- a/Service/BudgetService.cs +++ b/Service/BudgetService.cs @@ -19,6 +19,11 @@ public interface IBudgetService Task GetArchiveSummaryAsync(int year, int month); Task UpdateArchiveSummaryAsync(int year, int month, string? summary); + + /// + /// 获取指定周期的存款预算信息 + /// + Task GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type); } public class BudgetService( @@ -86,6 +91,12 @@ public class BudgetService( return dtos.Where(dto => dto != null).Cast().ToList(); } + public async Task GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type) + { + var referenceDate = new DateTime(year, month, 1); + return await GetVirtualSavingsDtoAsync(type, referenceDate); + } + public async Task GetCategoryStatsAsync(BudgetCategory category, DateTime referenceDate) { var budgets = await GetListAsync(referenceDate); @@ -204,13 +215,14 @@ public class BudgetService( totalLimit += itemLimit; // 当前值累加 + var selectedCategories = budget.SelectedCategories != null ? string.Join(',', budget.SelectedCategories) : string.Empty; var currentAmount = await CalculateCurrentAmountAsync(new() { Name = budget.Name, Type = budget.Type, Limit = budget.Limit, Category = budget.Category, - SelectedCategories = string.Join(',', budget.SelectedCategories), + SelectedCategories = selectedCategories, StartDate = new DateTime(referenceDate.Year, referenceDate.Month, 1) }, referenceDate); if (budget.Type == statType) @@ -242,6 +254,14 @@ public class BudgetService( var budgets = await GetListAsync(referenceDate); + var expenseSurplus = budgets + .Where(b => b.Category == BudgetCategory.Expense && !b.NoLimit && b.Type == BudgetPeriodType.Month) + .Sum(b => b.Limit - b.Current); + + var incomeSurplus = budgets + .Where(b => b.Category == BudgetCategory.Income && !b.NoLimit && b.Type == BudgetPeriodType.Month) + .Sum(b => b.Current - b.Limit); + var content = budgets.Select(b => new BudgetArchiveContent { Name = b.Name, @@ -260,6 +280,8 @@ public class BudgetService( { archive.Content = content; archive.ArchiveDate = DateTime.Now; + archive.ExpenseSurplus = expenseSurplus; + archive.IncomeSurplus = incomeSurplus; if (!await budgetArchiveRepository.UpdateAsync(archive)) { return "更新预算归档失败"; @@ -272,7 +294,9 @@ public class BudgetService( Year = year, Month = month, Content = content, - ArchiveDate = DateTime.Now + ArchiveDate = DateTime.Now, + ExpenseSurplus = expenseSurplus, + IncomeSurplus = incomeSurplus }; if (!await budgetArchiveRepository.AddAsync(archive)) @@ -578,10 +602,8 @@ public class BudgetService( } description.Append($"

收入合计: {incomeLimitAtPeriod:N0}

"); - // 新增:显示不记额收入明细 description.Append("

不记额收入明细

"); - if (noLimitIncomeItems.Count == 0) description.Append("

无不记额收入

"); - else + if (noLimitIncomeItems.Count > 0) { description.Append(""" @@ -637,11 +659,10 @@ public class BudgetService( } description.Append($"

支出合计: {expenseLimitAtPeriod:N0}

"); - // 新增:显示不记额支出明细 - description.Append("

不记额支出明细

"); - if (noLimitExpenseItems.Count == 0) description.Append("

无不记额支出

"); - else + if (noLimitExpenseItems.Count > 0) { + + description.Append("

不记额支出明细

"); description.Append("""
@@ -663,6 +684,7 @@ public class BudgetService( } description.Append("
"); } + description.Append($"

不记额支出合计: {noLimitExpenseAtPeriod:N0}

"); description.Append("

存款计划结论

"); @@ -671,12 +693,70 @@ public class BudgetService( var totalExpense = expenseLimitAtPeriod + noLimitExpenseAtPeriod; description.Append($"

计划收入 = 预算 {incomeLimitAtPeriod:N0} + 不记额 {noLimitIncomeAtPeriod:N0} = {totalIncome:N0}

"); description.Append($"

计划支出 = 预算 {expenseLimitAtPeriod:N0} + 不记额 {noLimitExpenseAtPeriod:N0} = {totalExpense:N0}

"); - description.Append($"

最终目标:{totalIncome - totalExpense:N0}

"); + + decimal historicalSurplus = 0; + if (periodType == BudgetPeriodType.Year) + { + var archives = await budgetArchiveRepository.GetArchivesByYearAsync(date.Year); + if (archives.Count > 0) + { + var expenseSurplus = archives.Sum(a => a.ExpenseSurplus); + var incomeSurplus = archives.Sum(a => a.IncomeSurplus); + historicalSurplus = expenseSurplus + incomeSurplus; + + description.Append("

历史月份盈亏

"); + description.Append(""" + + + + + + + + + + + """); + + foreach (var archive in archives) + { + var monthlyTotal = archive.ExpenseSurplus + archive.IncomeSurplus; + var monthlyClass = monthlyTotal >= 0 ? "income-value" : "expense-value"; + description.Append($""" + + + + + + + """); + } + + var totalClass = historicalSurplus >= 0 ? "income-value" : "expense-value"; + description.Append($""" + + + + + + +
月份支出结余收入结余合计
{archive.Month}月{archive.ExpenseSurplus:N0}{archive.IncomeSurplus:N0}{monthlyTotal:N0}
汇总{expenseSurplus:N0}{incomeSurplus:N0}{historicalSurplus:N0}
+ """); + } + var finalGoal = totalIncome - totalExpense + historicalSurplus; + description.Append($"

动态目标 = 计划盈余 {totalIncome - totalExpense:N0} + 历史盈亏 {historicalSurplus:N0} = {finalGoal:N0}

"); + } + else + { + description.Append($"

最终目标:{totalIncome - totalExpense:N0}

"); + } + + var finalLimit = periodType == BudgetPeriodType.Year ? (totalIncome - totalExpense + historicalSurplus) : (totalIncome - totalExpense); var virtualBudget = await BuildVirtualSavingsBudgetRecordAsync( periodType == BudgetPeriodType.Year ? -1 : -2, date, - totalIncome - totalExpense); // 修改:使用总金额 + finalLimit); // 计算实际发生的 收入 - 支出 var current = await CalculateCurrentAmountAsync(new BudgetRecord diff --git a/Web/src/api/budget.js b/Web/src/api/budget.js index 4720485..8876fd9 100644 --- a/Web/src/api/budget.js +++ b/Web/src/api/budget.js @@ -95,3 +95,17 @@ export function updateArchiveSummary(data) { data }) } + +/** + * 获取指定周期的存款预算信息 + * @param {number} year 年份 + * @param {number} month 月份 + * @param {number} type 周期类型 (1:Month, 2:Year) + */ +export function getSavingsBudget(year, month, type) { + return request({ + url: '/Budget/GetSavingsBudget', + method: 'get', + params: { year, month, type } + }) +} diff --git a/Web/src/components/Budget/BudgetCard.vue b/Web/src/components/Budget/BudgetCard.vue index b763f47..ac59911 100644 --- a/Web/src/components/Budget/BudgetCard.vue +++ b/Web/src/components/Budget/BudgetCard.vue @@ -133,6 +133,10 @@ + + diff --git a/Web/src/views/BudgetView.vue b/Web/src/views/BudgetView.vue index 648acf7..3fefa76 100644 --- a/Web/src/views/BudgetView.vue +++ b/Web/src/views/BudgetView.vue @@ -170,6 +170,33 @@ + + @@ -251,7 +278,7 @@