功能添加
This commit is contained in:
@@ -2,11 +2,7 @@
|
||||
|
||||
public interface IEmailMessageRepository : IBaseRepository<EmailMessage>
|
||||
{
|
||||
Task<EmailMessage?> ExistsAsync(
|
||||
string from,
|
||||
string subject,
|
||||
DateTime receivedDate,
|
||||
string body);
|
||||
Task<EmailMessage?> ExistsAsync(string md5);
|
||||
|
||||
/// <summary>
|
||||
/// 分页获取邮件列表(游标分页)
|
||||
@@ -25,14 +21,10 @@ public interface IEmailMessageRepository : IBaseRepository<EmailMessage>
|
||||
|
||||
public class EmailMessageRepository(IFreeSql freeSql) : BaseRepository<EmailMessage>(freeSql), IEmailMessageRepository
|
||||
{
|
||||
public async Task<EmailMessage?> ExistsAsync(
|
||||
string from,
|
||||
string subject,
|
||||
DateTime receivedDate,
|
||||
string body)
|
||||
public async Task<EmailMessage?> ExistsAsync(string md5)
|
||||
{
|
||||
return await FreeSql.Select<EmailMessage>()
|
||||
.Where(m => m.From == from && m.Subject == subject && m.ReceivedDate == receivedDate && m.Body == body)
|
||||
.Where(e => e.Md5 == md5)
|
||||
.FirstAsync();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,24 +6,14 @@
|
||||
public interface ITransactionCategoryRepository : IBaseRepository<TransactionCategory>
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据类型获取所有顶级分类(Level=2,即类型下的分类)
|
||||
/// 根据类型获取所有分类
|
||||
/// </summary>
|
||||
Task<List<TransactionCategory>> GetTopLevelCategoriesByTypeAsync(TransactionType type);
|
||||
Task<List<TransactionCategory>> GetCategoriesByTypeAsync(TransactionType type);
|
||||
|
||||
/// <summary>
|
||||
/// 根据父分类ID获取子分类
|
||||
/// 根据名称和类型查找分类(防止重复)
|
||||
/// </summary>
|
||||
Task<List<TransactionCategory>> GetChildCategoriesAsync(long parentId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取完整的分类树(按类型)
|
||||
/// </summary>
|
||||
Task<List<TransactionCategoryTreeDto>> GetCategoryTreeAsync(TransactionType? type = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称和父ID查找分类(防止重复)
|
||||
/// </summary>
|
||||
Task<TransactionCategory?> GetByNameAndParentAsync(string name, long parentId, TransactionType type);
|
||||
Task<TransactionCategory?> GetByNameAndTypeAsync(string name, TransactionType type);
|
||||
|
||||
/// <summary>
|
||||
/// 检查分类是否被使用
|
||||
@@ -37,101 +27,23 @@ public interface ITransactionCategoryRepository : IBaseRepository<TransactionCat
|
||||
public class TransactionCategoryRepository(IFreeSql freeSql) : BaseRepository<TransactionCategory>(freeSql), ITransactionCategoryRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据类型获取所有顶级分类(Level=2)
|
||||
/// 根据类型获取所有分类
|
||||
/// </summary>
|
||||
public async Task<List<TransactionCategory>> GetTopLevelCategoriesByTypeAsync(TransactionType type)
|
||||
public async Task<List<TransactionCategory>> GetCategoriesByTypeAsync(TransactionType type)
|
||||
{
|
||||
return await FreeSql.Select<TransactionCategory>()
|
||||
.Where(c => c.Type == type && c.Level == 2 && c.IsEnabled)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.Where(c => c.Type == type)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据父分类ID获取子分类
|
||||
/// 根据名称和类型查找分类
|
||||
/// </summary>
|
||||
public async Task<List<TransactionCategory>> GetChildCategoriesAsync(long parentId)
|
||||
public async Task<TransactionCategory?> GetByNameAndTypeAsync(string name, TransactionType type)
|
||||
{
|
||||
return await FreeSql.Select<TransactionCategory>()
|
||||
.Where(c => c.ParentId == parentId && c.IsEnabled)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取完整的分类树
|
||||
/// </summary>
|
||||
public async Task<List<TransactionCategoryTreeDto>> GetCategoryTreeAsync(TransactionType? type = null)
|
||||
{
|
||||
var query = FreeSql.Select<TransactionCategory>()
|
||||
.Where(c => c.IsEnabled);
|
||||
|
||||
if (type.HasValue)
|
||||
{
|
||||
query = query.Where(c => c.Type == type.Value);
|
||||
}
|
||||
|
||||
var allCategories = await query
|
||||
.OrderBy(c => c.Type)
|
||||
.OrderBy(c => c.SortOrder)
|
||||
.OrderBy(c => c.Name)
|
||||
.ToListAsync();
|
||||
|
||||
// 构建树形结构(Level 2为根节点,即各个分类)
|
||||
var result = new List<TransactionCategoryTreeDto>();
|
||||
var level2Categories = allCategories.Where(c => c.Level == 2).ToList();
|
||||
|
||||
foreach (var category in level2Categories)
|
||||
{
|
||||
var treeNode = new TransactionCategoryTreeDto
|
||||
{
|
||||
Id = category.Id,
|
||||
Name = category.Name,
|
||||
Type = category.Type,
|
||||
Level = category.Level,
|
||||
Icon = category.Icon,
|
||||
Children = BuildChildrenTree(category.Id, allCategories)
|
||||
};
|
||||
result.Add(treeNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归构建子分类树
|
||||
/// </summary>
|
||||
private List<TransactionCategoryTreeDto> BuildChildrenTree(long parentId, List<TransactionCategory> allCategories)
|
||||
{
|
||||
var children = allCategories.Where(c => c.ParentId == parentId).ToList();
|
||||
var result = new List<TransactionCategoryTreeDto>();
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
var treeNode = new TransactionCategoryTreeDto
|
||||
{
|
||||
Id = child.Id,
|
||||
Name = child.Name,
|
||||
Type = child.Type,
|
||||
Level = child.Level,
|
||||
Icon = child.Icon,
|
||||
Children = BuildChildrenTree(child.Id, allCategories)
|
||||
};
|
||||
result.Add(treeNode);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称和父ID查找分类
|
||||
/// </summary>
|
||||
public async Task<TransactionCategory?> GetByNameAndParentAsync(string name, long parentId, TransactionType type)
|
||||
{
|
||||
return await FreeSql.Select<TransactionCategory>()
|
||||
.Where(c => c.Name == name && c.ParentId == parentId && c.Type == type)
|
||||
.Where(c => c.Name == name && c.Type == type)
|
||||
.FirstAsync();
|
||||
}
|
||||
|
||||
@@ -140,35 +52,13 @@ public class TransactionCategoryRepository(IFreeSql freeSql) : BaseRepository<Tr
|
||||
/// </summary>
|
||||
public async Task<bool> IsCategoryInUseAsync(long categoryId)
|
||||
{
|
||||
// 检查是否有交易记录使用此分类
|
||||
var category = await GetByIdAsync(categoryId);
|
||||
if (category == null) return false;
|
||||
|
||||
// 根据层级检查不同的字段
|
||||
var count = category.Level switch
|
||||
{
|
||||
2 => await FreeSql.Select<TransactionRecord>()
|
||||
.Where(r => r.Classify == category.Name && r.Type == category.Type)
|
||||
.CountAsync(),
|
||||
3 => await FreeSql.Select<TransactionRecord>()
|
||||
.Where(r => r.SubClassify == category.Name && r.Type == category.Type)
|
||||
.CountAsync(),
|
||||
_ => 0
|
||||
};
|
||||
var count = await FreeSql.Select<TransactionRecord>()
|
||||
.Where(r => r.Classify == category.Name && r.Type == category.Type)
|
||||
.CountAsync();
|
||||
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分类树DTO
|
||||
/// </summary>
|
||||
public class TransactionCategoryTreeDto
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public TransactionType Type { get; set; }
|
||||
public int Level { get; set; }
|
||||
public string? Icon { get; set; }
|
||||
public List<TransactionCategoryTreeDto> Children { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -26,11 +26,6 @@ public interface ITransactionRecordRepository : IBaseRepository<TransactionRecor
|
||||
/// </summary>
|
||||
Task<List<string>> GetDistinctClassifyAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有不同的交易子分类
|
||||
/// </summary>
|
||||
Task<List<string>> GetDistinctSubClassifyAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定月份每天的消费统计
|
||||
/// </summary>
|
||||
@@ -73,6 +68,30 @@ public interface ITransactionRecordRepository : IBaseRepository<TransactionRecor
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <returns>未分类账单列表</returns>
|
||||
Task<List<TransactionRecord>> GetUnclassifiedAsync(int pageSize = 10);
|
||||
|
||||
/// <summary>
|
||||
/// 获取按交易摘要(Reason)分组的统计信息(支持分页)
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页码,从1开始</param>
|
||||
/// <param name="pageSize">每页数量</param>
|
||||
/// <returns>分组统计列表和总数</returns>
|
||||
Task<(List<ReasonGroupDto> list, int total)> GetReasonGroupsAsync(int pageIndex = 1, int pageSize = 20);
|
||||
|
||||
/// <summary>
|
||||
/// 按摘要批量更新交易记录的分类
|
||||
/// </summary>
|
||||
/// <param name="reason">交易摘要</param>
|
||||
/// <param name="type">交易类型</param>
|
||||
/// <param name="classify">分类名称</param>
|
||||
/// <returns>更新的记录数量</returns>
|
||||
Task<int> BatchUpdateByReasonAsync(string reason, TransactionType type, string classify);
|
||||
|
||||
/// <summary>
|
||||
/// 根据关键词查询交易记录(模糊匹配Reason字段)
|
||||
/// </summary>
|
||||
/// <param name="keyword">关键词</param>
|
||||
/// <returns>匹配的交易记录列表</returns>
|
||||
Task<List<TransactionRecord>> QueryBySqlAsync(string sql);
|
||||
}
|
||||
|
||||
public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<TransactionRecord>(freeSql), ITransactionRecordRepository
|
||||
@@ -100,7 +119,6 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
|
||||
{
|
||||
query = query.Where(t => t.Reason.Contains(searchKeyword) ||
|
||||
t.Classify.Contains(searchKeyword) ||
|
||||
t.SubClassify.Contains(searchKeyword) ||
|
||||
t.Card.Contains(searchKeyword) ||
|
||||
t.ImportFrom.Contains(searchKeyword));
|
||||
}
|
||||
@@ -135,14 +153,6 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
|
||||
.ToListAsync(t => t.Classify);
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetDistinctSubClassifyAsync()
|
||||
{
|
||||
return await FreeSql.Select<TransactionRecord>()
|
||||
.Where(t => !string.IsNullOrEmpty(t.SubClassify))
|
||||
.Distinct()
|
||||
.ToListAsync(t => t.SubClassify);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, (int count, decimal amount)>> GetDailyStatisticsAsync(int year, int month)
|
||||
{
|
||||
var startDate = new DateTime(year, month, 1);
|
||||
@@ -209,4 +219,93 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository<Tran
|
||||
.Page(1, pageSize)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<(List<ReasonGroupDto> list, int total)> GetReasonGroupsAsync(int pageIndex = 1, int pageSize = 20)
|
||||
{
|
||||
// 先按照Reason分组,统计每个Reason的数量
|
||||
var groups = await FreeSql.Select<TransactionRecord>()
|
||||
.Where(t => !string.IsNullOrEmpty(t.Reason))
|
||||
.Where(t => string.IsNullOrEmpty(t.Classify)) // 只统计未分类的
|
||||
.GroupBy(t => t.Reason)
|
||||
.ToListAsync(g => new
|
||||
{
|
||||
Reason = g.Key,
|
||||
Count = g.Count()
|
||||
});
|
||||
|
||||
// 按数量降序排序
|
||||
var sortedGroups = groups.OrderByDescending(g => g.Count).ToList();
|
||||
var total = sortedGroups.Count;
|
||||
|
||||
// 分页
|
||||
var pagedGroups = sortedGroups
|
||||
.Skip((pageIndex - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToList();
|
||||
|
||||
// 为每个分组获取示例记录
|
||||
var result = new List<ReasonGroupDto>();
|
||||
foreach (var group in pagedGroups)
|
||||
{
|
||||
var sample = await FreeSql.Select<TransactionRecord>()
|
||||
.Where(t => t.Reason == group.Reason)
|
||||
.FirstAsync();
|
||||
|
||||
if (sample != null)
|
||||
{
|
||||
result.Add(new ReasonGroupDto
|
||||
{
|
||||
Reason = group.Reason,
|
||||
Count = (int)group.Count,
|
||||
SampleType = sample.Type,
|
||||
SampleClassify = sample.Classify ?? string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (result, total);
|
||||
}
|
||||
|
||||
public async Task<int> BatchUpdateByReasonAsync(string reason, TransactionType type, string classify)
|
||||
{
|
||||
return await FreeSql.Update<TransactionRecord>()
|
||||
.Set(t => t.Type, type)
|
||||
.Set(t => t.Classify, classify)
|
||||
.Where(t => t.Reason == reason)
|
||||
.ExecuteAffrowsAsync();
|
||||
}
|
||||
|
||||
public async Task<List<TransactionRecord>> QueryBySqlAsync(string sql)
|
||||
{
|
||||
return await FreeSql.Select<TransactionRecord>()
|
||||
.Where(sql)
|
||||
.OrderByDescending(t => t.OccurredAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按Reason分组统计DTO
|
||||
/// </summary>
|
||||
public class ReasonGroupDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 交易摘要
|
||||
/// </summary>
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 该摘要的记录数量
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 示例交易类型(该分组中第一条记录的类型)
|
||||
/// </summary>
|
||||
public TransactionType SampleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 示例分类(该分组中第一条记录的分类)
|
||||
/// </summary>
|
||||
public string SampleClassify { get; set; } = string.Empty;
|
||||
}
|
||||
Reference in New Issue
Block a user