Files
EmailBill/WebApi/Controllers/TransactionRecordController.cs
SunCheng 3e18283e52 1
2026-02-09 19:25:51 +08:00

614 lines
19 KiB
C#
Raw 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 Service.AI;
using Service.Transaction;
namespace WebApi.Controllers;
[ApiController]
[Route("api/[controller]/[action]")]
public class TransactionRecordController(
ITransactionRecordRepository transactionRepository,
ISmartHandleService smartHandleService,
ILogger<TransactionRecordController> logger
) : ControllerBase
{
/// <summary>
/// 获取交易记录列表(分页)
/// </summary>
[HttpGet]
public async Task<PagedResponse<TransactionRecord>> GetListAsync(
[FromQuery] int pageIndex = 1,
[FromQuery] int pageSize = 20,
[FromQuery] string? searchKeyword = null,
[FromQuery] string? classify = null,
[FromQuery] int? type = null,
[FromQuery] int? year = null,
[FromQuery] int? month = null,
[FromQuery] DateTime? startDate = null,
[FromQuery] DateTime? endDate = null,
[FromQuery] string? reason = null,
[FromQuery] bool sortByAmount = false
)
{
try
{
var classifies = string.IsNullOrWhiteSpace(classify)
? null
: classify.Split(',', StringSplitOptions.RemoveEmptyEntries);
TransactionType? transactionType = type.HasValue ? (TransactionType)type.Value : null;
var list = await transactionRepository.QueryAsync(
year: year,
month: month,
startDate: startDate,
endDate: endDate,
type: transactionType,
classifies: classifies,
searchKeyword: searchKeyword,
reason: reason,
pageIndex: pageIndex,
pageSize: pageSize,
sortByAmount: sortByAmount);
var total = await transactionRepository.CountAsync(
year: year,
month: month,
startDate: startDate,
endDate: endDate,
type: transactionType,
classifies: classifies,
searchKeyword: searchKeyword,
reason: reason);
return new PagedResponse<TransactionRecord>
{
Success = true,
Data = list.ToArray(),
Total = (int)total
};
}
catch (Exception ex)
{
logger.LogError(ex, "获取交易记录列表失败,页码: {PageIndex}, 页大小: {PageSize}", pageIndex, pageSize);
return PagedResponse<TransactionRecord>.Fail($"获取交易记录列表失败: {ex.Message}");
}
}
/// <summary>
/// 获取待确认分类的交易记录列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetUnconfirmedListAsync()
{
try
{
var list = await transactionRepository.GetUnconfirmedRecordsAsync();
return list.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取待确认分类交易列表失败");
return $"获取待确认分类交易列表失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
}
/// <summary>
/// 全部确认待确认的交易分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> ConfirmAllUnconfirmedAsync([FromBody] ConfirmAllUnconfirmedRequestDto request)
{
try
{
var count = await transactionRepository.ConfirmAllUnconfirmedAsync(request.Ids);
return count.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "全部确认待确认分类失败");
return $"全部确认待确认分类失败: {ex.Message}".Fail<int>();
}
}
/// <summary>
/// 根据ID获取交易记录详情
/// </summary>
[HttpGet("{id}")]
public async Task<BaseResponse<TransactionRecord>> GetByIdAsync(long id)
{
try
{
var transaction = await transactionRepository.GetByIdAsync(id);
if (transaction == null)
{
return "交易记录不存在".Fail<TransactionRecord>();
}
return transaction.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取交易记录详情失败交易ID: {TransactionId}", id);
return $"获取交易记录详情失败: {ex.Message}".Fail<TransactionRecord>();
}
}
/// <summary>
/// 根据邮件ID获取交易记录列表
/// </summary>
[HttpGet("{emailId}")]
public async Task<BaseResponse<List<TransactionRecord>>> GetByEmailIdAsync(long emailId)
{
try
{
var transactions = await transactionRepository.GetByEmailIdAsync(emailId);
return transactions.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取邮件交易记录失败邮件ID: {EmailId}", emailId);
return $"获取邮件交易记录失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
}
/// <summary>
/// 创建交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionDto dto)
{
try
{
// 解析日期字符串
if (!DateTime.TryParse(dto.OccurredAt, out var occurredAt))
{
return "交易时间格式不正确".Fail();
}
var transaction = new TransactionRecord
{
OccurredAt = occurredAt,
Reason = dto.Reason ?? string.Empty,
Amount = dto.Amount,
Type = dto.Type,
Classify = dto.Classify ?? string.Empty,
ImportFrom = "手动录入",
ImportNo = Guid.NewGuid().ToString("N"),
Card = "手动",
EmailMessageId = 0 // 手动录入的记录EmailMessageId 设为 0
};
var result = await transactionRepository.AddAsync(transaction);
if (result)
{
return BaseResponse.Done();
}
return "创建交易记录失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "创建交易记录失败,交易信息: {@TransactionDto}", dto);
return $"创建交易记录失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 更新交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionDto dto)
{
try
{
var transaction = await transactionRepository.GetByIdAsync(dto.Id);
if (transaction == null)
{
return "交易记录不存在".Fail();
}
// 更新可编辑字段
transaction.Reason = dto.Reason ?? string.Empty;
transaction.Amount = dto.Amount;
transaction.Balance = dto.Balance;
transaction.Type = dto.Type;
transaction.Classify = dto.Classify ?? string.Empty;
// 更新交易时间
if (!string.IsNullOrEmpty(dto.OccurredAt) && DateTime.TryParse(dto.OccurredAt, out var occurredAt))
{
transaction.OccurredAt = occurredAt;
}
// 清除待确认状态
transaction.UnconfirmedClassify = null;
transaction.UnconfirmedType = null;
var success = await transactionRepository.UpdateAsync(transaction);
if (success)
{
return BaseResponse.Done();
}
return "更新交易记录失败".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "更新交易记录失败交易ID: {TransactionId}, 交易信息: {@TransactionDto}", dto.Id, dto);
return $"更新交易记录失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 删除交易记录
/// </summary>
[HttpPost]
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
{
try
{
var success = await transactionRepository.DeleteAsync(id);
if (success)
{
return BaseResponse.Done();
}
return "删除交易记录失败,记录不存在".Fail();
}
catch (Exception ex)
{
logger.LogError(ex, "删除交易记录失败交易ID: {TransactionId}", id);
return $"删除交易记录失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 智能分析账单(流式输出)
/// </summary>
public async Task AnalyzeBillAsync([FromBody] BillAnalysisRequest request)
{
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
if (string.IsNullOrWhiteSpace(request.UserInput))
{
await WriteEventAsync("<div class='error-message'>请输入分析内容</div>");
return;
}
await smartHandleService.AnalyzeBillAsync(request.UserInput, async void (chunk) =>
{
try
{
await WriteEventAsync(chunk);
}
catch (Exception e)
{
logger.LogError(e, "流式写入账单分析结果失败");
}
});
}
/// <summary>
/// 获取指定日期的交易记录
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetByDateAsync([FromQuery] string date)
{
try
{
if (!DateTime.TryParse(date, out var targetDate))
{
return "日期格式不正确".Fail<List<TransactionRecord>>();
}
// 获取当天的开始和结束时间
var startDate = targetDate.Date;
var endDate = startDate.AddDays(1);
var records = await transactionRepository.QueryAsync(startDate: startDate, endDate: endDate);
return records.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取指定日期的交易记录失败,日期: {Date}", date);
return $"获取指定日期的交易记录失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
}
/// <summary>
/// 获取未分类的账单数量
/// </summary>
[HttpGet]
public async Task<BaseResponse<int>> GetUnclassifiedCountAsync()
{
try
{
var count = (int)await transactionRepository.CountAsync();
return count.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取未分类账单数量失败");
return $"获取未分类账单数量失败: {ex.Message}".Fail<int>();
}
}
/// <summary>
/// 获取未分类的账单列表
/// </summary>
[HttpGet]
public async Task<BaseResponse<List<TransactionRecord>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
{
try
{
var records = await transactionRepository.GetUnclassifiedAsync(pageSize);
return records.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "获取未分类账单列表失败");
return $"获取未分类账单列表失败: {ex.Message}".Fail<List<TransactionRecord>>();
}
}
/// <summary>
/// 智能分类 - 使用AI对账单进行分类流式响应
/// </summary>
[HttpPost]
public async Task SmartClassifyAsync([FromBody] SmartClassifyRequest request)
{
Response.ContentType = "text/event-stream";
Response.Headers.Append("Cache-Control", "no-cache");
Response.Headers.Append("Connection", "keep-alive");
// 验证账单ID列表
if (request.TransactionIds == null || request.TransactionIds.Count == 0)
{
await WriteEventAsync("error", "请提供要分类的账单ID");
return;
}
await smartHandleService.SmartClassifyAsync(request.TransactionIds.ToArray(), async void (chunk) =>
{
try
{
var (eventType, content) = chunk;
await TrySetUnconfirmedAsync(eventType, content);
await WriteEventAsync(eventType, content);
}
catch (Exception e)
{
logger.LogError(e, "流式写入智能分类结果失败");
}
});
await Response.Body.FlushAsync();
}
private async Task TrySetUnconfirmedAsync(string eventType, string content)
{
if (eventType != "data")
{
return;
}
try
{
var jsonObject = JsonSerializer.Deserialize<JsonObject>(content);
var id = jsonObject?["id"]?.GetValue<long>() ?? 0;
var classify = jsonObject?["Classify"]?.GetValue<string>() ?? string.Empty;
var typeValue = jsonObject?["Type"]?.GetValue<int>() ?? -1;
if (id == 0 || typeValue == -1 || string.IsNullOrEmpty(classify))
{
logger.LogWarning("解析智能分类结果时,发现无效数据,内容: {Content}", content);
return;
}
var record = await transactionRepository.GetByIdAsync(id);
if (record == null)
{
logger.LogWarning("解析智能分类结果时未找到对应的交易记录ID: {Id}", id);
return;
}
record.UnconfirmedClassify = classify;
record.UnconfirmedType = (TransactionType)typeValue;
var success = await transactionRepository.UpdateAsync(record);
if (!success)
{
logger.LogWarning("解析智能分类结果时更新交易记录失败ID: {Id}", id);
}
}
catch (Exception ex)
{
logger.LogError(ex, "解析智能分类结果失败,内容: {Content}", content);
}
}
/// <summary>
/// 批量更新账单分类
/// </summary>
[HttpPost]
public async Task<BaseResponse> BatchUpdateClassifyAsync([FromBody] List<BatchUpdateClassifyItem> items)
{
try
{
var successCount = 0;
var failCount = 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 (record.Type == item.Type)
{
record.UnconfirmedType = TransactionType.None;
}
var success = await transactionRepository.UpdateAsync(record);
if (success)
successCount++;
else
failCount++;
}
else
{
failCount++;
}
}
return $"批量更新完成,成功 {successCount} 条,失败 {failCount} 条".Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "批量更新分类失败");
return $"批量更新分类失败: {ex.Message}".Fail();
}
}
/// <summary>
/// 按摘要批量更新分类
/// </summary>
[HttpPost]
public async Task<BaseResponse<int>> BatchUpdateByReasonAsync([FromBody] BatchUpdateByReasonDto dto)
{
try
{
var count = await transactionRepository.BatchUpdateByReasonAsync(dto.Reason, dto.Type, dto.Classify);
return count.Ok($"成功更新 {count} 条记录");
}
catch (Exception ex)
{
logger.LogError(ex, "按摘要批量更新分类失败,摘要: {Reason}", dto.Reason);
return $"按摘要批量更新分类失败: {ex.Message}".Fail<int>();
}
}
/// <summary>
/// 一句话录账解析
/// </summary>
[HttpPost]
public async Task<BaseResponse<TransactionParseResult>> ParseOneLine([FromBody] ParseOneLineRequestDto request)
{
if (string.IsNullOrEmpty(request.Text))
{
return "请求参数缺失text".Fail<TransactionParseResult>();
}
try
{
var result = await smartHandleService.ParseOneLineBillAsync(request.Text);
if (result == null)
{
return "AI解析失败".Fail<TransactionParseResult>();
}
return result.Ok();
}
catch (Exception ex)
{
logger.LogError(ex, "一句话录账解析失败,文本: {Text}", request.Text);
return ("AI解析失败: " + ex.Message).Fail<TransactionParseResult>();
}
}
private async Task WriteEventAsync(string eventType, string data)
{
var message = $"event: {eventType}\ndata: {data}\n\n";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
}
private async Task WriteEventAsync(string data)
{
var message = $"data: {data}\n\n";
await Response.WriteAsync(message);
await Response.Body.FlushAsync();
}
}
/// <summary>
/// 创建交易记录DTO
/// </summary>
public record CreateTransactionDto(
string OccurredAt,
string? Reason,
decimal Amount,
TransactionType Type,
string? Classify
);
/// <summary>
/// 更新交易记录DTO
/// </summary>
public record UpdateTransactionDto(
long Id,
string? Reason,
decimal Amount,
decimal Balance,
TransactionType Type,
string? Classify,
string? OccurredAt = null
);
/// <summary>
/// 智能分类请求DTO
/// </summary>
public record SmartClassifyRequest(
List<long>? TransactionIds = null
);
/// <summary>
/// 批量更新分类项DTO
/// </summary>
public record BatchUpdateClassifyItem(
long Id,
string? Classify,
TransactionType? Type = null
);
/// <summary>
/// 按摘要批量更新DTO
/// </summary>
public record BatchUpdateByReasonDto(
string Reason,
TransactionType Type,
string Classify
);
/// <summary>
/// 账单分析请求DTO
/// </summary>
public record BillAnalysisRequest(
string UserInput
);
public record ParseOneLineRequestDto(
string Text
);
public record ConfirmAllUnconfirmedRequestDto(
long[] Ids
);