Files
EmailBill/Application/BudgetApplication.cs

303 lines
9.3 KiB
C#
Raw Normal View History

2026-02-10 17:49:19 +08:00
using Service.Budget;
namespace Application;
/// <summary>
/// 预算应用服务接口
/// </summary>
public interface IBudgetApplication
{
/// <summary>
/// 获取预算列表
/// </summary>
Task<List<BudgetResponse>> GetListAsync(DateTime referenceDate);
/// <summary>
/// 获取分类统计信息(月度和年度)
/// </summary>
Task<BudgetCategoryStatsResponse> GetCategoryStatsAsync(BudgetCategory category, DateTime referenceDate);
/// <summary>
/// 获取未被预算覆盖的分类统计信息
/// </summary>
Task<List<UncoveredCategoryResponse>> GetUncoveredCategoriesAsync(BudgetCategory category, DateTime? referenceDate = null);
/// <summary>
/// 获取归档总结
/// </summary>
Task<string?> GetArchiveSummaryAsync(DateTime referenceDate);
/// <summary>
/// 获取指定周期的存款预算信息
/// </summary>
Task<BudgetResponse?> GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type);
/// <summary>
/// 删除预算
/// </summary>
Task DeleteByIdAsync(long id);
/// <summary>
/// 创建预算
/// </summary>
Task<long> CreateAsync(CreateBudgetRequest request);
/// <summary>
/// 更新预算
/// </summary>
Task UpdateAsync(UpdateBudgetRequest request);
}
/// <summary>
/// 预算应用服务实现
/// </summary>
public class BudgetApplication(
IBudgetService budgetService,
IBudgetRepository budgetRepository
) : IBudgetApplication
{
public async Task<List<BudgetResponse>> GetListAsync(DateTime referenceDate)
{
var results = await budgetService.GetListAsync(referenceDate);
// 排序: 刚性支出优先 → 按分类 → 按类型 → 按使用率 → 按名称
return results
.OrderByDescending(b => b.IsMandatoryExpense)
.ThenBy(b => b.Category)
.ThenBy(b => b.Type)
.ThenByDescending(b => b.Limit > 0 ? b.Current / b.Limit : 0)
.ThenBy(b => b.Name)
.Select(MapToResponse)
.ToList();
}
public async Task<BudgetCategoryStatsResponse> GetCategoryStatsAsync(
BudgetCategory category,
DateTime referenceDate)
{
var result = await budgetService.GetCategoryStatsAsync(category, referenceDate);
return new BudgetCategoryStatsResponse
{
Month = new BudgetStatsDetail
{
Limit = result.Month.Limit,
Current = result.Month.Current,
Remaining = result.Month.Limit - result.Month.Current,
UsagePercentage = result.Month.Rate
},
Year = new BudgetStatsDetail
{
Limit = result.Year.Limit,
Current = result.Year.Current,
Remaining = result.Year.Limit - result.Year.Current,
UsagePercentage = result.Year.Rate
}
};
}
public async Task<List<UncoveredCategoryResponse>> GetUncoveredCategoriesAsync(
BudgetCategory category,
DateTime? referenceDate = null)
{
var results = await budgetService.GetUncoveredCategoriesAsync(category, referenceDate);
return results.Select(r => new UncoveredCategoryResponse
{
Category = r.Category,
Amount = r.TotalAmount,
Count = r.TransactionCount
}).ToList();
}
public async Task<string?> GetArchiveSummaryAsync(DateTime referenceDate)
{
return await budgetService.GetArchiveSummaryAsync(referenceDate.Year, referenceDate.Month);
}
public async Task<BudgetResponse?> GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type)
{
var result = await budgetService.GetSavingsBudgetAsync(year, month, type);
return result == null ? null : MapToResponse(result);
}
public async Task DeleteByIdAsync(long id)
{
var success = await budgetRepository.DeleteAsync(id);
if (!success)
{
throw new BusinessException("删除预算失败,记录不存在");
}
}
public async Task<long> CreateAsync(CreateBudgetRequest request)
{
// 业务验证
await ValidateCreateRequestAsync(request);
// 不记额预算的金额强制设为0
var limit = request.NoLimit ? 0 : request.Limit;
var budget = new BudgetRecord
{
Name = request.Name,
Type = request.Type,
Limit = limit,
Category = request.Category,
SelectedCategories = string.Join(",", request.SelectedCategories),
StartDate = request.StartDate ?? DateTime.Now,
NoLimit = request.NoLimit,
IsMandatoryExpense = request.IsMandatoryExpense
};
// 验证分类冲突
await ValidateBudgetCategoriesAsync(budget);
var success = await budgetRepository.AddAsync(budget);
if (!success)
{
throw new BusinessException("创建预算失败");
}
return budget.Id;
}
public async Task UpdateAsync(UpdateBudgetRequest request)
{
var budget = await budgetRepository.GetByIdAsync(request.Id);
if (budget == null)
{
throw new NotFoundException("预算不存在");
}
// 业务验证
await ValidateUpdateRequestAsync(request);
// 不记额预算的金额强制设为0
var limit = request.NoLimit ? 0 : request.Limit;
budget.Name = request.Name;
budget.Type = request.Type;
budget.Limit = limit;
budget.Category = request.Category;
budget.SelectedCategories = string.Join(",", request.SelectedCategories);
budget.NoLimit = request.NoLimit;
budget.IsMandatoryExpense = request.IsMandatoryExpense;
if (request.StartDate.HasValue)
{
budget.StartDate = request.StartDate.Value;
}
// 验证分类冲突
await ValidateBudgetCategoriesAsync(budget);
var success = await budgetRepository.UpdateAsync(budget);
if (!success)
{
throw new BusinessException("更新预算失败");
}
}
#region Private Methods
/// <summary>
/// 映射到响应DTO
/// </summary>
private static BudgetResponse MapToResponse(BudgetResult result)
{
// 解析StartDate字符串为DateTime
DateTime.TryParse(result.StartDate, out var startDate);
return new BudgetResponse
{
Id = result.Id,
Name = result.Name,
Type = result.Type,
Limit = result.Limit,
Current = result.Current,
Category = result.Category,
SelectedCategories = result.SelectedCategories,
StartDate = startDate,
NoLimit = result.NoLimit,
IsMandatoryExpense = result.IsMandatoryExpense,
UsagePercentage = result.Limit > 0 ? result.Current / result.Limit * 100 : 0
};
}
/// <summary>
/// 验证创建请求
/// </summary>
private static Task ValidateCreateRequestAsync(CreateBudgetRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ValidationException("预算名称不能为空");
}
if (!request.NoLimit && request.Limit <= 0)
{
throw new ValidationException("预算金额必须大于0");
}
if (request.SelectedCategories.Length == 0)
{
throw new ValidationException("请至少选择一个分类");
}
return Task.CompletedTask;
}
/// <summary>
/// 验证更新请求
/// </summary>
private static Task ValidateUpdateRequestAsync(UpdateBudgetRequest request)
{
if (string.IsNullOrWhiteSpace(request.Name))
{
throw new ValidationException("预算名称不能为空");
}
if (!request.NoLimit && request.Limit <= 0)
{
throw new ValidationException("预算金额必须大于0");
}
if (request.SelectedCategories.Length == 0)
{
throw new ValidationException("请至少选择一个分类");
}
return Task.CompletedTask;
}
/// <summary>
/// 验证预算分类从Controller迁移的业务逻辑
/// </summary>
private async Task ValidateBudgetCategoriesAsync(BudgetRecord record)
{
// 验证不记额预算必须是年度预算
if (record.NoLimit && record.Type != BudgetPeriodType.Year)
{
throw new ValidationException("不记额预算只能设置为年度预算。");
}
var allBudgets = await budgetRepository.GetAllAsync();
var recordSelectedCategories = record.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);
foreach (var budget in allBudgets)
{
var selectedCategories = budget.SelectedCategories.Split(',', StringSplitOptions.RemoveEmptyEntries);
if (budget.Id != record.Id)
{
if (budget.Category == record.Category &&
recordSelectedCategories.Intersect(selectedCategories).Any())
{
throw new ValidationException($"和 {budget.Name} 存在分类冲突,请调整相关分类。");
}
}
}
}
#endregion
}