namespace Repository; public interface ITransactionRecordRepository : IBaseRepository { Task ExistsByEmailMessageIdAsync(long emailMessageId, DateTime occurredAt); Task ExistsByImportNoAsync(string importNo, string importFrom); /// /// 分页获取交易记录列表(游标分页) /// /// 上一页最后一条记录的发生时间 /// 上一页最后一条记录的ID /// 每页数量 /// 搜索关键词(搜索交易摘要和分类) /// 交易记录列表、最后发生时间和最后ID Task<(List list, DateTime? lastOccurredAt, long lastId)> GetPagedListAsync(DateTime? lastOccurredAt, long? lastId, int pageSize = 20, string? searchKeyword = null); /// /// 获取总数 /// Task GetTotalCountAsync(); /// /// 获取所有不同的交易分类 /// Task> GetDistinctClassifyAsync(); /// /// 获取所有不同的交易子分类 /// Task> GetDistinctSubClassifyAsync(); /// /// 获取指定月份每天的消费统计 /// /// 年份 /// 月份 /// 每天的消费笔数和金额 Task> GetDailyStatisticsAsync(int year, int month); /// /// 获取指定日期范围内的交易记录 /// /// 开始日期 /// 结束日期 /// 交易记录列表 Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate); /// /// 获取指定邮件的交易记录数量 /// /// 邮件ID /// 交易记录数量 Task GetCountByEmailIdAsync(long emailMessageId); /// /// 获取指定邮件的交易记录列表 /// /// 邮件ID /// 交易记录列表 Task> GetByEmailIdAsync(long emailMessageId); } public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository(freeSql), ITransactionRecordRepository { public async Task ExistsByEmailMessageIdAsync(long emailMessageId, DateTime occurredAt) { return await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId && t.OccurredAt == occurredAt) .FirstAsync(); } public async Task ExistsByImportNoAsync(string importNo, string importFrom) { return await FreeSql.Select() .Where(t => t.ImportNo == importNo && t.ImportFrom == importFrom) .FirstAsync(); } public async Task<(List list, DateTime? lastOccurredAt, long lastId)> GetPagedListAsync(DateTime? lastOccurredAt, long? lastId, int pageSize = 20, string? searchKeyword = null) { var query = FreeSql.Select(); // 如果提供了搜索关键词,则添加搜索条件 if (!string.IsNullOrWhiteSpace(searchKeyword)) { query = query.Where(t => t.Reason.Contains(searchKeyword) || t.Classify.Contains(searchKeyword) || t.SubClassify.Contains(searchKeyword) || t.Card.Contains(searchKeyword) || t.ImportFrom.Contains(searchKeyword)); } // 如果提供了游标,则获取小于游标位置的记录 if (lastOccurredAt.HasValue && lastId.HasValue) { query = query.Where(t => t.OccurredAt < lastOccurredAt.Value || (t.OccurredAt == lastOccurredAt.Value && t.Id < lastId.Value)); } var list = await query .OrderByDescending(t => t.OccurredAt) .OrderByDescending(t => t.Id) .Page(1, pageSize) .ToListAsync(); var lastRecord = list.Count > 0 ? list.Last() : null; return (list, lastRecord?.OccurredAt, lastRecord?.Id ?? 0); } public async Task GetTotalCountAsync() { return await FreeSql.Select().CountAsync(); } public async Task> GetDistinctClassifyAsync() { return await FreeSql.Select() .Where(t => !string.IsNullOrEmpty(t.Classify)) .Distinct() .ToListAsync(t => t.Classify); } public async Task> GetDistinctSubClassifyAsync() { return await FreeSql.Select() .Where(t => !string.IsNullOrEmpty(t.SubClassify)) .Distinct() .ToListAsync(t => t.SubClassify); } public async Task> GetDailyStatisticsAsync(int year, int month) { var startDate = new DateTime(year, month, 1); var endDate = startDate.AddMonths(1); var records = await FreeSql.Select() .Where(t => t.OccurredAt >= startDate && t.OccurredAt < endDate) .Where(t => t.Type == TransactionType.Expense || t.Type == TransactionType.Income) // 统计消费和收入 .ToListAsync(); var statistics = records .GroupBy(t => t.OccurredAt.ToString("yyyy-MM-dd")) .ToDictionary( g => g.Key, g => { // 分别统计收入和支出 var income = g.Where(t => t.Type == TransactionType.Income).Sum(t => t.Amount); var expense = g.Where(t => t.Type == TransactionType.Expense).Sum(t => t.Amount); // 净额 = 收入 - 支出(消费大于收入时为负数) var netAmount = income - expense; return (count: g.Count(), amount: netAmount); } ); return statistics; } public async Task> GetByDateRangeAsync(DateTime startDate, DateTime endDate) { return await FreeSql.Select() .Where(t => t.OccurredAt >= startDate && t.OccurredAt <= endDate) .OrderBy(t => t.OccurredAt) .ToListAsync(); } public async Task GetCountByEmailIdAsync(long emailMessageId) { return (int)await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId) .CountAsync(); } public async Task> GetByEmailIdAsync(long emailMessageId) { return await FreeSql.Select() .Where(t => t.EmailMessageId == emailMessageId) .OrderBy(t => t.OccurredAt) .ToListAsync(); } }