Files
EmailBill/Application/TransactionApplication.cs
SunCheng 51172e8c5a
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
fix
2026-02-11 13:00:01 +08:00

388 lines
12 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
};
}
}