2026-02-10 17:49:19 +08:00
|
|
|
|
using Application.Dto.Statistics;
|
|
|
|
|
|
using Service.Transaction;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Application;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 交易统计应用服务接口
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public interface ITransactionStatisticsApplication
|
|
|
|
|
|
{
|
|
|
|
|
|
// === 新统一接口(推荐使用) ===
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取每日统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<List<DailyStatisticsDto>> GetDailyStatisticsByRangeAsync(DateTime startDate, DateTime endDate, string? savingClassify = null);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取汇总统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<MonthlyStatistics> GetSummaryByRangeAsync(DateTime startDate, DateTime endDate);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取分类统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<List<CategoryStatistics>> GetCategoryStatisticsByRangeAsync(DateTime startDate, DateTime endDate, TransactionType type);
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取趋势统计数据
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
Task<List<TrendStatistics>> GetTrendStatisticsAsync(int startYear, int startMonth, int monthCount);
|
|
|
|
|
|
|
|
|
|
|
|
// === 旧接口(保留用于向后兼容,建议迁移到新接口) ===
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetDailyStatisticsByRangeAsync")]
|
|
|
|
|
|
Task<List<DailyStatisticsDto>> GetDailyStatisticsAsync(int year, int month);
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetDailyStatisticsByRangeAsync")]
|
|
|
|
|
|
Task<List<DailyStatisticsDto>> GetWeeklyStatisticsAsync(DateTime startDate, DateTime endDate);
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetSummaryByRangeAsync")]
|
|
|
|
|
|
Task<MonthlyStatistics> GetRangeStatisticsAsync(DateTime startDate, DateTime endDate);
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetSummaryByRangeAsync")]
|
|
|
|
|
|
Task<MonthlyStatistics> GetMonthlyStatisticsAsync(int year, int month);
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetCategoryStatisticsByRangeAsync")]
|
|
|
|
|
|
Task<List<CategoryStatistics>> GetCategoryStatisticsAsync(int year, int month, TransactionType type);
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("请使用 GetCategoryStatisticsByRangeAsync")]
|
|
|
|
|
|
Task<List<CategoryStatistics>> GetCategoryStatisticsByDateRangeAsync(string startDate, string endDate, TransactionType type);
|
|
|
|
|
|
|
|
|
|
|
|
Task<(List<ReasonGroupDto> list, int total)> GetReasonGroupsAsync(int pageIndex, int pageSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 交易统计应用服务实现
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class TransactionStatisticsApplication(
|
|
|
|
|
|
ITransactionStatisticsService statisticsService,
|
2026-02-11 13:00:01 +08:00
|
|
|
|
IConfigService configService
|
2026-02-10 17:49:19 +08:00
|
|
|
|
) : ITransactionStatisticsApplication
|
|
|
|
|
|
{
|
|
|
|
|
|
// === 新统一接口实现 ===
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取每日统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<List<DailyStatisticsDto>> GetDailyStatisticsByRangeAsync(DateTime startDate, DateTime endDate, string? savingClassify = null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 如果未指定 savingClassify,从配置读取
|
|
|
|
|
|
savingClassify ??= await configService.GetConfigByKeyAsync<string>("SavingsCategories");
|
|
|
|
|
|
|
|
|
|
|
|
var statistics = await statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, savingClassify);
|
|
|
|
|
|
|
|
|
|
|
|
return statistics.Select(s => new DailyStatisticsDto(
|
|
|
|
|
|
DateTime.Parse(s.Key).Day,
|
|
|
|
|
|
s.Value.count,
|
|
|
|
|
|
s.Value.expense,
|
|
|
|
|
|
s.Value.income,
|
|
|
|
|
|
s.Value.saving
|
|
|
|
|
|
)).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取汇总统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<MonthlyStatistics> GetSummaryByRangeAsync(DateTime startDate, DateTime endDate)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetSummaryByRangeAsync(startDate, endDate);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按日期范围获取分类统计(新统一接口)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public async Task<List<CategoryStatistics>> GetCategoryStatisticsByRangeAsync(DateTime startDate, DateTime endDate, TransactionType type)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetCategoryStatisticsByDateRangeAsync(startDate, endDate, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// === 旧接口实现(保留用于向后兼容) ===
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<DailyStatisticsDto>> GetDailyStatisticsAsync(int year, int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
var savingClassify = await configService.GetConfigByKeyAsync<string>("SavingsCategories");
|
|
|
|
|
|
var statistics = await statisticsService.GetDailyStatisticsAsync(year, month, savingClassify);
|
|
|
|
|
|
|
|
|
|
|
|
return statistics.Select(s => new DailyStatisticsDto(
|
|
|
|
|
|
DateTime.Parse(s.Key).Day, // 从完整日期字符串 "yyyy-MM-dd" 中提取 day
|
|
|
|
|
|
s.Value.count,
|
|
|
|
|
|
s.Value.expense,
|
|
|
|
|
|
s.Value.income,
|
|
|
|
|
|
s.Value.saving
|
|
|
|
|
|
)).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<DailyStatisticsDto>> GetWeeklyStatisticsAsync(DateTime startDate, DateTime endDate)
|
|
|
|
|
|
{
|
|
|
|
|
|
var savingClassify = await configService.GetConfigByKeyAsync<string>("SavingsCategories");
|
|
|
|
|
|
var statistics = await statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, savingClassify);
|
|
|
|
|
|
|
|
|
|
|
|
return statistics.Select(s => new DailyStatisticsDto(
|
|
|
|
|
|
DateTime.Parse(s.Key).Day, // 从完整日期字符串 "yyyy-MM-dd" 中提取 day
|
|
|
|
|
|
s.Value.count,
|
|
|
|
|
|
s.Value.expense,
|
|
|
|
|
|
s.Value.income,
|
|
|
|
|
|
s.Value.saving
|
|
|
|
|
|
)).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<MonthlyStatistics> GetRangeStatisticsAsync(DateTime startDate, DateTime endDate)
|
|
|
|
|
|
{
|
|
|
|
|
|
var records = await statisticsService.GetDailyStatisticsByRangeAsync(startDate, endDate, null);
|
|
|
|
|
|
|
|
|
|
|
|
var totalExpense = records.Sum(r => r.Value.expense);
|
|
|
|
|
|
var totalIncome = records.Sum(r => r.Value.income);
|
|
|
|
|
|
var totalCount = records.Sum(r => r.Value.count);
|
|
|
|
|
|
var expenseCount = records.Count(r => r.Value.expense > 0);
|
|
|
|
|
|
var incomeCount = records.Count(r => r.Value.income > 0);
|
|
|
|
|
|
|
|
|
|
|
|
return new MonthlyStatistics
|
|
|
|
|
|
{
|
|
|
|
|
|
Year = startDate.Year,
|
|
|
|
|
|
Month = startDate.Month,
|
|
|
|
|
|
TotalExpense = totalExpense,
|
|
|
|
|
|
TotalIncome = totalIncome,
|
|
|
|
|
|
Balance = totalIncome - totalExpense,
|
|
|
|
|
|
ExpenseCount = expenseCount,
|
|
|
|
|
|
IncomeCount = incomeCount,
|
|
|
|
|
|
TotalCount = totalCount
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<MonthlyStatistics> GetMonthlyStatisticsAsync(int year, int month)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetMonthlyStatisticsAsync(year, month);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<CategoryStatistics>> GetCategoryStatisticsAsync(int year, int month, TransactionType type)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetCategoryStatisticsAsync(year, month, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<CategoryStatistics>> GetCategoryStatisticsByDateRangeAsync(string startDate, string endDate, TransactionType type)
|
|
|
|
|
|
{
|
|
|
|
|
|
var start = DateTime.Parse(startDate);
|
|
|
|
|
|
var end = DateTime.Parse(endDate);
|
|
|
|
|
|
return await statisticsService.GetCategoryStatisticsByDateRangeAsync(start, end, type);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<List<TrendStatistics>> GetTrendStatisticsAsync(int startYear, int startMonth, int monthCount)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetTrendStatisticsAsync(startYear, startMonth, monthCount);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<(List<ReasonGroupDto> list, int total)> GetReasonGroupsAsync(int pageIndex, int pageSize)
|
|
|
|
|
|
{
|
|
|
|
|
|
return await statisticsService.GetReasonGroupsAsync(pageIndex, pageSize);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|