All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 4m27s
Docker Build & Deploy / Deploy to Production (push) Successful in 7s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 1s
Docker Build & Deploy / WeChat Notification (push) Successful in 1s
388 lines
12 KiB
C#
388 lines
12 KiB
C#
using Application.Dto.Transaction;
|
||
using Service.AI;
|
||
|
||
namespace Application;
|
||
|
||
/// <summary>
|
||
/// 交易应用服务接口
|
||
/// </summary>
|
||
public interface ITransactionApplication
|
||
{
|
||
/// <summary>
|
||
/// 获取交易记录列表(分页)
|
||
/// </summary>
|
||
Task<PagedResult<TransactionResponse>> GetListAsync(TransactionQueryRequest request);
|
||
|
||
/// <summary>
|
||
/// 根据ID获取交易记录详情
|
||
/// </summary>
|
||
Task<TransactionResponse> GetByIdAsync(long id);
|
||
|
||
/// <summary>
|
||
/// 创建交易记录
|
||
/// </summary>
|
||
Task CreateAsync(CreateTransactionRequest request);
|
||
|
||
/// <summary>
|
||
/// 更新交易记录
|
||
/// </summary>
|
||
Task UpdateAsync(UpdateTransactionRequest request);
|
||
|
||
/// <summary>
|
||
/// 删除交易记录
|
||
/// </summary>
|
||
Task DeleteByIdAsync(long id);
|
||
|
||
/// <summary>
|
||
/// 根据邮件ID获取交易记录列表
|
||
/// </summary>
|
||
Task<List<TransactionResponse>> GetByEmailIdAsync(long emailId);
|
||
|
||
/// <summary>
|
||
/// 根据日期获取交易记录列表
|
||
/// </summary>
|
||
Task<List<TransactionResponse>> GetByDateAsync(DateTime date);
|
||
|
||
/// <summary>
|
||
/// 获取未确认的交易记录列表
|
||
/// </summary>
|
||
Task<List<TransactionResponse>> GetUnconfirmedListAsync();
|
||
|
||
/// <summary>
|
||
/// 获取未确认的交易记录数量
|
||
/// </summary>
|
||
Task<int> GetUnconfirmedCountAsync();
|
||
|
||
/// <summary>
|
||
/// 获取未分类的账单数量
|
||
/// </summary>
|
||
Task<int> GetUnclassifiedCountAsync();
|
||
|
||
/// <summary>
|
||
/// 获取未分类的账单列表
|
||
/// </summary>
|
||
Task<List<TransactionResponse>> GetUnclassifiedAsync(int pageSize);
|
||
|
||
/// <summary>
|
||
/// 确认所有未确认的记录
|
||
/// </summary>
|
||
Task<int> ConfirmAllUnconfirmedAsync(long[] ids);
|
||
|
||
/// <summary>
|
||
/// 智能分类(AI分类,支持回调)
|
||
/// </summary>
|
||
Task SmartClassifyAsync(long[] transactionIds, Action<(string type, string data)> onChunk);
|
||
|
||
/// <summary>
|
||
/// 一句话录账解析
|
||
/// </summary>
|
||
Task<TransactionParseResult?> ParseOneLineAsync(string text);
|
||
|
||
/// <summary>
|
||
/// 账单分析(AI分析,支持回调)
|
||
/// </summary>
|
||
Task AnalyzeBillAsync(string userInput, Action<string> onChunk);
|
||
|
||
/// <summary>
|
||
/// 批量更新分类
|
||
/// </summary>
|
||
Task<int> BatchUpdateClassifyAsync(List<BatchUpdateClassifyItem> items);
|
||
|
||
/// <summary>
|
||
/// 按摘要批量更新分类
|
||
/// </summary>
|
||
Task<int> BatchUpdateByReasonAsync(BatchUpdateByReasonRequest request);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 交易应用服务实现
|
||
/// </summary>
|
||
public class TransactionApplication(
|
||
ITransactionRecordRepository transactionRepository,
|
||
ISmartHandleService smartHandleService
|
||
) : ITransactionApplication
|
||
{
|
||
public async Task<PagedResult<TransactionResponse>> GetListAsync(TransactionQueryRequest request)
|
||
{
|
||
var classifies = string.IsNullOrWhiteSpace(request.Classify)
|
||
? null
|
||
: request.Classify.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||
|
||
TransactionType? transactionType = request.Type.HasValue ? (TransactionType)request.Type.Value : null;
|
||
|
||
var list = await transactionRepository.QueryAsync(
|
||
year: request.Year,
|
||
month: request.Month,
|
||
startDate: request.StartDate,
|
||
endDate: request.EndDate,
|
||
type: transactionType,
|
||
classifies: classifies,
|
||
searchKeyword: request.SearchKeyword,
|
||
reason: request.Reason,
|
||
pageIndex: request.PageIndex,
|
||
pageSize: request.PageSize,
|
||
sortByAmount: request.SortByAmount);
|
||
|
||
var total = await transactionRepository.CountAsync(
|
||
year: request.Year,
|
||
month: request.Month,
|
||
startDate: request.StartDate,
|
||
endDate: request.EndDate,
|
||
type: transactionType,
|
||
classifies: classifies,
|
||
searchKeyword: request.SearchKeyword,
|
||
reason: request.Reason);
|
||
|
||
return new PagedResult<TransactionResponse>
|
||
{
|
||
Data = list.Select(MapToResponse).ToArray(),
|
||
Total = (int)total
|
||
};
|
||
}
|
||
|
||
public async Task<TransactionResponse> GetByIdAsync(long id)
|
||
{
|
||
var transaction = await transactionRepository.GetByIdAsync(id);
|
||
if (transaction == null)
|
||
{
|
||
throw new NotFoundException("交易记录不存在");
|
||
}
|
||
|
||
return MapToResponse(transaction);
|
||
}
|
||
|
||
public async Task CreateAsync(CreateTransactionRequest request)
|
||
{
|
||
// 解析日期字符串
|
||
if (!DateTime.TryParse(request.OccurredAt, out var occurredAt))
|
||
{
|
||
throw new ValidationException("交易时间格式不正确");
|
||
}
|
||
|
||
var transaction = new TransactionRecord
|
||
{
|
||
OccurredAt = occurredAt,
|
||
Reason = request.Reason ?? string.Empty,
|
||
Amount = request.Amount,
|
||
Type = request.Type,
|
||
Classify = request.Classify ?? string.Empty,
|
||
ImportFrom = "手动录入",
|
||
ImportNo = Guid.NewGuid().ToString("N"),
|
||
Card = "手动",
|
||
EmailMessageId = 0
|
||
};
|
||
|
||
var result = await transactionRepository.AddAsync(transaction);
|
||
if (!result)
|
||
{
|
||
throw new BusinessException("创建交易记录失败");
|
||
}
|
||
}
|
||
|
||
public async Task UpdateAsync(UpdateTransactionRequest request)
|
||
{
|
||
var transaction = await transactionRepository.GetByIdAsync(request.Id);
|
||
if (transaction == null)
|
||
{
|
||
throw new NotFoundException("交易记录不存在");
|
||
}
|
||
|
||
// 更新可编辑字段
|
||
transaction.Reason = request.Reason ?? string.Empty;
|
||
transaction.Amount = request.Amount;
|
||
transaction.Balance = request.Balance;
|
||
transaction.Type = request.Type;
|
||
transaction.Classify = request.Classify ?? string.Empty;
|
||
|
||
// 更新交易时间
|
||
if (!string.IsNullOrEmpty(request.OccurredAt) && DateTime.TryParse(request.OccurredAt, out var occurredAt))
|
||
{
|
||
transaction.OccurredAt = occurredAt;
|
||
}
|
||
|
||
// 清除待确认状态
|
||
transaction.UnconfirmedClassify = null;
|
||
transaction.UnconfirmedType = null;
|
||
|
||
var success = await transactionRepository.UpdateAsync(transaction);
|
||
if (!success)
|
||
{
|
||
throw new BusinessException("更新交易记录失败");
|
||
}
|
||
}
|
||
|
||
public async Task DeleteByIdAsync(long id)
|
||
{
|
||
var success = await transactionRepository.DeleteAsync(id);
|
||
if (!success)
|
||
{
|
||
throw new BusinessException("删除交易记录失败,记录不存在");
|
||
}
|
||
}
|
||
|
||
public async Task<List<TransactionResponse>> GetByEmailIdAsync(long emailId)
|
||
{
|
||
var transactions = await transactionRepository.GetByEmailIdAsync(emailId);
|
||
return transactions.Select(MapToResponse).ToList();
|
||
}
|
||
|
||
public async Task<List<TransactionResponse>> GetByDateAsync(DateTime date)
|
||
{
|
||
// 获取当天的开始和结束时间
|
||
var startDate = date.Date;
|
||
var endDate = startDate.AddDays(1);
|
||
|
||
var records = await transactionRepository.QueryAsync(startDate: startDate, endDate: endDate);
|
||
return records.Select(MapToResponse).ToList();
|
||
}
|
||
|
||
public async Task<List<TransactionResponse>> GetUnconfirmedListAsync()
|
||
{
|
||
var records = await transactionRepository.GetUnconfirmedRecordsAsync();
|
||
return records.Select(MapToResponse).ToList();
|
||
}
|
||
|
||
public async Task<int> GetUnconfirmedCountAsync()
|
||
{
|
||
var records = await transactionRepository.GetUnconfirmedRecordsAsync();
|
||
return records.Count;
|
||
}
|
||
|
||
public async Task<int> GetUnclassifiedCountAsync()
|
||
{
|
||
return (int)await transactionRepository.CountAsync();
|
||
}
|
||
|
||
public async Task<List<TransactionResponse>> GetUnclassifiedAsync(int pageSize)
|
||
{
|
||
var records = await transactionRepository.GetUnclassifiedAsync(pageSize);
|
||
return records.Select(MapToResponse).ToList();
|
||
}
|
||
|
||
public async Task<int> ConfirmAllUnconfirmedAsync(long[] ids)
|
||
{
|
||
if (ids == null || ids.Length == 0)
|
||
{
|
||
throw new ValidationException("请提供要确认的交易ID列表");
|
||
}
|
||
|
||
return await transactionRepository.ConfirmAllUnconfirmedAsync(ids);
|
||
}
|
||
|
||
public async Task SmartClassifyAsync(long[] transactionIds, Action<(string type, string data)> onChunk)
|
||
{
|
||
// 验证
|
||
if (transactionIds == null || transactionIds.Length == 0)
|
||
{
|
||
throw new ValidationException("请提供要分类的账单ID");
|
||
}
|
||
|
||
// 调用Service进行智能分类
|
||
await smartHandleService.SmartClassifyAsync(transactionIds, onChunk);
|
||
}
|
||
|
||
public async Task<TransactionParseResult?> ParseOneLineAsync(string text)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(text))
|
||
{
|
||
throw new ValidationException("解析文本不能为空");
|
||
}
|
||
|
||
var result = await smartHandleService.ParseOneLineBillAsync(text);
|
||
if (result == null)
|
||
{
|
||
throw new BusinessException("AI解析失败");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
public async Task AnalyzeBillAsync(string userInput, Action<string> onChunk)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(userInput))
|
||
{
|
||
throw new ValidationException("请输入分析内容");
|
||
}
|
||
|
||
await smartHandleService.AnalyzeBillAsync(userInput, onChunk);
|
||
}
|
||
|
||
public async Task<int> BatchUpdateClassifyAsync(List<BatchUpdateClassifyItem> items)
|
||
{
|
||
if (items == null || items.Count == 0)
|
||
{
|
||
throw new ValidationException("请提供要更新的记录");
|
||
}
|
||
|
||
var successCount = 0;
|
||
|
||
foreach (var item in items)
|
||
{
|
||
var record = await transactionRepository.GetByIdAsync(item.Id);
|
||
if (record != null)
|
||
{
|
||
record.Classify = item.Classify ?? string.Empty;
|
||
|
||
// 如果提供了Type,也更新Type
|
||
if (item.Type.HasValue)
|
||
{
|
||
record.Type = item.Type.Value;
|
||
}
|
||
|
||
// 清除待确认状态
|
||
if (!string.IsNullOrEmpty(record.Classify))
|
||
{
|
||
record.UnconfirmedClassify = null;
|
||
}
|
||
|
||
if (item.Type.HasValue && record.Type == item.Type.Value)
|
||
{
|
||
record.UnconfirmedType = null;
|
||
}
|
||
|
||
var success = await transactionRepository.UpdateAsync(record);
|
||
if (success)
|
||
{
|
||
successCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
return successCount;
|
||
}
|
||
|
||
public async Task<int> BatchUpdateByReasonAsync(BatchUpdateByReasonRequest request)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(request.Reason))
|
||
{
|
||
throw new ValidationException("摘要不能为空");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(request.Classify))
|
||
{
|
||
throw new ValidationException("分类不能为空");
|
||
}
|
||
|
||
return await transactionRepository.BatchUpdateByReasonAsync(
|
||
request.Reason,
|
||
request.Type,
|
||
request.Classify);
|
||
}
|
||
|
||
private static TransactionResponse MapToResponse(TransactionRecord record)
|
||
{
|
||
return new TransactionResponse
|
||
{
|
||
Id = record.Id,
|
||
OccurredAt = record.OccurredAt,
|
||
Reason = record.Reason,
|
||
Amount = record.Amount,
|
||
Balance = record.Balance,
|
||
Type = record.Type,
|
||
Classify = record.Classify,
|
||
UnconfirmedClassify = record.UnconfirmedClassify,
|
||
UnconfirmedType = record.UnconfirmedType
|
||
};
|
||
}
|
||
}
|