using Service.Budget; namespace Application; /// /// 预算应用服务接口 /// public interface IBudgetApplication { /// /// 获取预算列表 /// Task> GetListAsync(DateTime referenceDate); /// /// 获取分类统计信息(月度和年度) /// Task GetCategoryStatsAsync(BudgetCategory category, DateTime referenceDate); /// /// 获取未被预算覆盖的分类统计信息 /// Task> GetUncoveredCategoriesAsync(BudgetCategory category, DateTime? referenceDate = null); /// /// 获取归档总结 /// Task GetArchiveSummaryAsync(DateTime referenceDate); /// /// 获取指定周期的存款预算信息 /// Task GetSavingsBudgetAsync(int year, int month, BudgetPeriodType type); /// /// 删除预算 /// Task DeleteByIdAsync(long id); /// /// 创建预算 /// Task CreateAsync(CreateBudgetRequest request); /// /// 更新预算 /// Task UpdateAsync(UpdateBudgetRequest request); } /// /// 预算应用服务实现 /// public class BudgetApplication( IBudgetService budgetService, IBudgetRepository budgetRepository ) : IBudgetApplication { public async Task> 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 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, Trend = result.Month.Trend, Description = result.Month.Description }, Year = new BudgetStatsDetail { Limit = result.Year.Limit, Current = result.Year.Current, Remaining = result.Year.Limit - result.Year.Current, UsagePercentage = result.Year.Rate, Trend = result.Year.Trend, Description = result.Year.Description } }; } public async Task> 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 GetArchiveSummaryAsync(DateTime referenceDate) { return await budgetService.GetArchiveSummaryAsync(referenceDate.Year, referenceDate.Month); } public async Task 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 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 /// /// 映射到响应DTO /// 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 }; } /// /// 验证创建请求 /// 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; } /// /// 验证更新请求 /// 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; } /// /// 验证预算分类(从Controller迁移的业务逻辑) /// 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 }