fix
This commit is contained in:
302
Application/BudgetApplication.cs
Normal file
302
Application/BudgetApplication.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user