历史预算计算
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 22s
Docker Build & Deploy / Deploy to Production (push) Successful in 6s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s
This commit is contained in:
@@ -38,6 +38,11 @@ public class BudgetArchive : BaseEntity
|
|||||||
|
|
||||||
public record BudgetArchiveContent
|
public record BudgetArchiveContent
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 预算ID
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预算名称
|
/// 预算名称
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public class BudgetService(
|
|||||||
var (start, end) = GetPeriodRange(DateTime.Now, BudgetPeriodType.Month, referenceDate);
|
var (start, end) = GetPeriodRange(DateTime.Now, BudgetPeriodType.Month, referenceDate);
|
||||||
return [.. archive.Content.Select(c => new BudgetResult
|
return [.. archive.Content.Select(c => new BudgetResult
|
||||||
{
|
{
|
||||||
|
Id = c.Id,
|
||||||
Name = c.Name,
|
Name = c.Name,
|
||||||
Type = c.Type,
|
Type = c.Type,
|
||||||
Limit = c.Limit,
|
Limit = c.Limit,
|
||||||
@@ -158,7 +159,6 @@ public class BudgetService(
|
|||||||
return archive?.Summary;
|
return archive?.Summary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task<BudgetStatsDto> CalculateCategoryStatsAsync(
|
private async Task<BudgetStatsDto> CalculateCategoryStatsAsync(
|
||||||
List<BudgetResult> budgets,
|
List<BudgetResult> budgets,
|
||||||
BudgetCategory category,
|
BudgetCategory category,
|
||||||
@@ -340,6 +340,7 @@ public class BudgetService(
|
|||||||
|
|
||||||
var content = budgets.Select(b => new BudgetArchiveContent
|
var content = budgets.Select(b => new BudgetArchiveContent
|
||||||
{
|
{
|
||||||
|
Id = b.Id,
|
||||||
Name = b.Name,
|
Name = b.Name,
|
||||||
Type = b.Type,
|
Type = b.Type,
|
||||||
Limit = b.Limit,
|
Limit = b.Limit,
|
||||||
@@ -621,26 +622,72 @@ public class BudgetService(
|
|||||||
decimal noLimitIncomeAtPeriod = 0; // 新增:不记额收入汇总
|
decimal noLimitIncomeAtPeriod = 0; // 新增:不记额收入汇总
|
||||||
decimal noLimitExpenseAtPeriod = 0; // 新增:不记额支出汇总
|
decimal noLimitExpenseAtPeriod = 0; // 新增:不记额支出汇总
|
||||||
|
|
||||||
var incomeItems = 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 Total)>();
|
var expenseItems = new List<(string Name, decimal Limit, decimal Factor, decimal Historical, decimal Total)>();
|
||||||
var noLimitIncomeItems = new List<(string Name, decimal Amount)>(); // 新增
|
var noLimitIncomeItems = new List<(string Name, decimal Amount)>(); // 新增
|
||||||
var noLimitExpenseItems = 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<long>();
|
||||||
foreach (var b in allBudgets)
|
foreach (var b in allBudgets)
|
||||||
{
|
{
|
||||||
if (b.Category == BudgetCategory.Savings) continue;
|
if (b.Category == BudgetCategory.Savings) continue;
|
||||||
|
|
||||||
// 折算系数:根据当前请求的 periodType (Year 或 Month),将预算 b 的 Limit 折算过来
|
processedIds.Add(b.Id);
|
||||||
decimal factor = 1.0m;
|
decimal factor = 1.0m;
|
||||||
|
decimal historicalAmount = 0m;
|
||||||
|
var historicalMonths = new List<int>();
|
||||||
|
|
||||||
if (periodType == BudgetPeriodType.Year)
|
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)
|
else if (periodType == BudgetPeriodType.Month)
|
||||||
{
|
{
|
||||||
@@ -656,12 +703,11 @@ public class BudgetService(
|
|||||||
factor = 0; // 其他周期暂不计算虚拟存款
|
factor = 0; // 其他周期暂不计算虚拟存款
|
||||||
}
|
}
|
||||||
|
|
||||||
if (factor <= 0) continue;
|
if (factor <= 0 && historicalAmount <= 0) continue;
|
||||||
|
|
||||||
// 新增:处理不记额预算
|
// 处理不记额预算
|
||||||
if (b.NoLimit)
|
if (b.NoLimit)
|
||||||
{
|
{
|
||||||
// 不记额预算:计算实际发生的金额
|
|
||||||
var actualAmount = await CalculateCurrentAmountAsync(b, date);
|
var actualAmount = await CalculateCurrentAmountAsync(b, date);
|
||||||
if (b.Category == BudgetCategory.Income)
|
if (b.Category == BudgetCategory.Income)
|
||||||
{
|
{
|
||||||
@@ -676,17 +722,63 @@ public class BudgetService(
|
|||||||
}
|
}
|
||||||
else
|
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)
|
if (b.Category == BudgetCategory.Income)
|
||||||
{
|
{
|
||||||
incomeLimitAtPeriod += subtotal;
|
incomeLimitAtPeriod += subtotal;
|
||||||
incomeItems.Add((b.Name, b.Limit, factor, subtotal));
|
incomeItems.Add((displayName, b.Limit, factor, historicalAmount, subtotal));
|
||||||
}
|
}
|
||||||
else if (b.Category == BudgetCategory.Expense)
|
else if (b.Category == BudgetCategory.Expense)
|
||||||
{
|
{
|
||||||
expenseLimitAtPeriod += subtotal;
|
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("<p>无收入预算</p>");
|
if (incomeItems.Count == 0) description.Append("<p>无收入预算</p>");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
description.Append("""
|
// 根据是否有历史数据决定表格列
|
||||||
<table>
|
var hasHistoricalData = incomeItems.Any(i => i.Historical > 0);
|
||||||
<thead>
|
|
||||||
<tr>
|
if (hasHistoricalData)
|
||||||
<th>名称</th>
|
|
||||||
<th>金额</th>
|
|
||||||
<th>折算</th>
|
|
||||||
<th>合计</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
""");
|
|
||||||
foreach (var item in incomeItems)
|
|
||||||
{
|
{
|
||||||
description.Append($"""
|
description.Append("""
|
||||||
<tr>
|
<table>
|
||||||
<td>{item.Name}</td>
|
<thead>
|
||||||
<td>{item.Limit:N0}</td>
|
<tr>
|
||||||
<td>{item.Factor:0.##}</td>
|
<th>名称</th>
|
||||||
<td><span class='income-value'>{item.Total:N0}</span></td>
|
<th>当前预算</th>
|
||||||
</tr>
|
<th>剩余月数</th>
|
||||||
|
<th>历史归档</th>
|
||||||
|
<th>合计</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
""");
|
""");
|
||||||
|
foreach (var item in incomeItems)
|
||||||
|
{
|
||||||
|
description.Append($"""
|
||||||
|
<tr>
|
||||||
|
<td>{item.Name}</td>
|
||||||
|
<td>{item.Limit:N0}</td>
|
||||||
|
<td>{item.Factor:0.##}</td>
|
||||||
|
<td>{item.Historical:N0}</td>
|
||||||
|
<td><span class='income-value'>{item.Total:N0}</span></td>
|
||||||
|
</tr>
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
description.Append("""
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>名称</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>折算</th>
|
||||||
|
<th>合计</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
""");
|
||||||
|
foreach (var item in incomeItems)
|
||||||
|
{
|
||||||
|
description.Append($"""
|
||||||
|
<tr>
|
||||||
|
<td>{item.Name}</td>
|
||||||
|
<td>{item.Limit:N0}</td>
|
||||||
|
<td>{item.Factor:0.##}</td>
|
||||||
|
<td><span class='income-value'>{item.Total:N0}</span></td>
|
||||||
|
</tr>
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
description.Append("</tbody></table>");
|
description.Append("</tbody></table>");
|
||||||
}
|
}
|
||||||
@@ -753,28 +879,62 @@ public class BudgetService(
|
|||||||
if (expenseItems.Count == 0) description.Append("<p>无支出预算</p>");
|
if (expenseItems.Count == 0) description.Append("<p>无支出预算</p>");
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
description.Append("""
|
// 根据是否有历史数据决定表格列
|
||||||
<table>
|
var hasHistoricalData = expenseItems.Any(i => i.Historical > 0);
|
||||||
<thead>
|
|
||||||
<tr>
|
if (hasHistoricalData)
|
||||||
<th>名称</th>
|
|
||||||
<th>金额</th>
|
|
||||||
<th>折算</th>
|
|
||||||
<th>合计</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
""");
|
|
||||||
foreach (var (Name, Limit, Factor, Total) in expenseItems)
|
|
||||||
{
|
{
|
||||||
description.Append($"""
|
description.Append("""
|
||||||
<tr>
|
<table>
|
||||||
<td>{Name}</td>
|
<thead>
|
||||||
<td>{Limit:N0}</td>
|
<tr>
|
||||||
<td>{Factor:0.##}</td>
|
<th>名称</th>
|
||||||
<td><span class='expense-value'>{Total:N0}</span></td>
|
<th>当前预算</th>
|
||||||
</tr>
|
<th>剩余月数</th>
|
||||||
|
<th>历史归档</th>
|
||||||
|
<th>合计</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
""");
|
""");
|
||||||
|
foreach (var item in expenseItems)
|
||||||
|
{
|
||||||
|
description.Append($"""
|
||||||
|
<tr>
|
||||||
|
<td>{item.Name}</td>
|
||||||
|
<td>{item.Limit:N0}</td>
|
||||||
|
<td>{item.Factor:0.##}</td>
|
||||||
|
<td>{item.Historical:N0}</td>
|
||||||
|
<td><span class='expense-value'>{item.Total:N0}</span></td>
|
||||||
|
</tr>
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
description.Append("""
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>名称</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>折算</th>
|
||||||
|
<th>合计</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
""");
|
||||||
|
foreach (var item in expenseItems)
|
||||||
|
{
|
||||||
|
description.Append($"""
|
||||||
|
<tr>
|
||||||
|
<td>{item.Name}</td>
|
||||||
|
<td>{item.Limit:N0}</td>
|
||||||
|
<td>{item.Factor:0.##}</td>
|
||||||
|
<td><span class='expense-value'>{item.Total:N0}</span></td>
|
||||||
|
</tr>
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
description.Append("</tbody></table>");
|
description.Append("</tbody></table>");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user