760 lines
25 KiB
C#
760 lines
25 KiB
C#
namespace WebApi.Controllers;
|
||
|
||
[ApiController]
|
||
[Route("api/[controller]/[action]")]
|
||
public class TransactionRecordController(
|
||
ITransactionRecordRepository transactionRepository,
|
||
ITransactionCategoryRepository categoryRepository,
|
||
IOpenAiService openAiService,
|
||
ILogger<TransactionRecordController> logger
|
||
) : ControllerBase
|
||
{
|
||
/// <summary>
|
||
/// 获取交易记录列表(分页)
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<PagedResponse<Entity.TransactionRecord>> GetListAsync(
|
||
[FromQuery] DateTime? lastOccurredAt = null,
|
||
[FromQuery] long? lastId = null,
|
||
[FromQuery] string? searchKeyword = null
|
||
)
|
||
{
|
||
try
|
||
{
|
||
var (list, lastTime, lastIdResult) = await transactionRepository.GetPagedListAsync(lastOccurredAt, lastId, 20, searchKeyword);
|
||
var total = await transactionRepository.GetTotalCountAsync();
|
||
|
||
return new PagedResponse<Entity.TransactionRecord>
|
||
{
|
||
Success = true,
|
||
Data = list.ToArray(),
|
||
Total = (int)total,
|
||
LastId = lastIdResult,
|
||
LastTime = lastTime
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取交易记录列表失败,时间: {LastTime}, ID: {LastId}", lastOccurredAt, lastId);
|
||
return PagedResponse<Entity.TransactionRecord>.Fail($"获取交易记录列表失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据ID获取交易记录详情
|
||
/// </summary>
|
||
[HttpGet("{id}")]
|
||
public async Task<BaseResponse<Entity.TransactionRecord>> GetByIdAsync(long id)
|
||
{
|
||
try
|
||
{
|
||
var transaction = await transactionRepository.GetByIdAsync(id);
|
||
if (transaction == null)
|
||
{
|
||
return BaseResponse<Entity.TransactionRecord>.Fail("交易记录不存在");
|
||
}
|
||
|
||
return new BaseResponse<Entity.TransactionRecord>
|
||
{
|
||
Success = true,
|
||
Data = transaction
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取交易记录详情失败,交易ID: {TransactionId}", id);
|
||
return BaseResponse<Entity.TransactionRecord>.Fail($"获取交易记录详情失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据邮件ID获取交易记录列表
|
||
/// </summary>
|
||
[HttpGet("{emailId}")]
|
||
public async Task<BaseResponse<List<Entity.TransactionRecord>>> GetByEmailIdAsync(long emailId)
|
||
{
|
||
try
|
||
{
|
||
var transactions = await transactionRepository.GetByEmailIdAsync(emailId);
|
||
return new BaseResponse<List<Entity.TransactionRecord>>
|
||
{
|
||
Success = true,
|
||
Data = transactions
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取邮件交易记录失败,邮件ID: {EmailId}", emailId);
|
||
return BaseResponse<List<Entity.TransactionRecord>>.Fail($"获取邮件交易记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建交易记录
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<BaseResponse> CreateAsync([FromBody] CreateTransactionDto dto)
|
||
{
|
||
try
|
||
{
|
||
// 解析日期字符串
|
||
if (!DateTime.TryParse(dto.OccurredAt, out var occurredAt))
|
||
{
|
||
return BaseResponse.Fail("交易时间格式不正确");
|
||
}
|
||
|
||
var transaction = new Entity.TransactionRecord
|
||
{
|
||
OccurredAt = occurredAt,
|
||
Reason = dto.Reason ?? string.Empty,
|
||
Amount = dto.Amount,
|
||
Type = dto.Type,
|
||
Classify = dto.Classify ?? string.Empty,
|
||
ImportFrom = "手动录入",
|
||
EmailMessageId = 0 // 手动录入的记录,EmailMessageId 设为 0
|
||
};
|
||
|
||
var result = await transactionRepository.AddAsync(transaction);
|
||
if (result)
|
||
{
|
||
return new BaseResponse
|
||
{
|
||
Success = true
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return BaseResponse.Fail("创建交易记录失败");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "创建交易记录失败,交易信息: {@TransactionDto}", dto);
|
||
return BaseResponse.Fail($"创建交易记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新交易记录
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<BaseResponse> UpdateAsync([FromBody] UpdateTransactionDto dto)
|
||
{
|
||
try
|
||
{
|
||
var transaction = await transactionRepository.GetByIdAsync(dto.Id);
|
||
if (transaction == null)
|
||
{
|
||
return BaseResponse.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;
|
||
|
||
var success = await transactionRepository.UpdateAsync(transaction);
|
||
if (success)
|
||
{
|
||
return new BaseResponse
|
||
{
|
||
Success = true
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return BaseResponse.Fail("更新交易记录失败");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "更新交易记录失败,交易ID: {TransactionId}, 交易信息: {@TransactionDto}", dto.Id, dto);
|
||
return BaseResponse.Fail($"更新交易记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除交易记录
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<BaseResponse> DeleteByIdAsync([FromQuery] long id)
|
||
{
|
||
try
|
||
{
|
||
var success = await transactionRepository.DeleteAsync(id);
|
||
if (success)
|
||
{
|
||
return new BaseResponse
|
||
{
|
||
Success = true
|
||
};
|
||
}
|
||
else
|
||
{
|
||
return BaseResponse.Fail("删除交易记录失败,记录不存在");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "删除交易记录失败,交易ID: {TransactionId}", id);
|
||
return BaseResponse.Fail($"删除交易记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定月份每天的消费统计
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<BaseResponse<List<DailyStatisticsDto>>> GetDailyStatisticsAsync(
|
||
[FromQuery] int year,
|
||
[FromQuery] int month
|
||
)
|
||
{
|
||
try
|
||
{
|
||
var statistics = await transactionRepository.GetDailyStatisticsAsync(year, month);
|
||
var result = statistics.Select(s => new DailyStatisticsDto(s.Key, s.Value.count, s.Value.amount)).ToList();
|
||
|
||
return new BaseResponse<List<DailyStatisticsDto>>
|
||
{
|
||
Success = true,
|
||
Data = result
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取日历统计数据失败,年份: {Year}, 月份: {Month}", year, month);
|
||
return BaseResponse<List<DailyStatisticsDto>>.Fail($"获取日历统计数据失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取指定日期的交易记录
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<BaseResponse<List<Entity.TransactionRecord>>> GetByDateAsync(
|
||
[FromQuery] string date
|
||
)
|
||
{
|
||
try
|
||
{
|
||
if (!DateTime.TryParse(date, out var targetDate))
|
||
{
|
||
return BaseResponse<List<Entity.TransactionRecord>>.Fail("日期格式不正确");
|
||
}
|
||
|
||
// 获取当天的开始和结束时间
|
||
var startDate = targetDate.Date;
|
||
var endDate = startDate.AddDays(1);
|
||
|
||
var records = await transactionRepository.GetByDateRangeAsync(startDate, endDate);
|
||
|
||
return new BaseResponse<List<Entity.TransactionRecord>>
|
||
{
|
||
Success = true,
|
||
Data = records
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取指定日期的交易记录失败,日期: {Date}", date);
|
||
return BaseResponse<List<Entity.TransactionRecord>>.Fail($"获取指定日期的交易记录失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取未分类的账单数量
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<BaseResponse<int>> GetUnclassifiedCountAsync()
|
||
{
|
||
try
|
||
{
|
||
var count = await transactionRepository.GetUnclassifiedCountAsync();
|
||
return new BaseResponse<int>
|
||
{
|
||
Success = true,
|
||
Data = count
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取未分类账单数量失败");
|
||
return BaseResponse<int>.Fail($"获取未分类账单数量失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取未分类的账单列表
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<BaseResponse<List<Entity.TransactionRecord>>> GetUnclassifiedAsync([FromQuery] int pageSize = 10)
|
||
{
|
||
try
|
||
{
|
||
var records = await transactionRepository.GetUnclassifiedAsync(pageSize);
|
||
return new BaseResponse<List<Entity.TransactionRecord>>
|
||
{
|
||
Success = true,
|
||
Data = records
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取未分类账单列表失败");
|
||
return BaseResponse<List<Entity.TransactionRecord>>.Fail($"获取未分类账单列表失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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");
|
||
|
||
try
|
||
{
|
||
// 验证账单ID列表
|
||
if (request.TransactionIds == null || request.TransactionIds.Count == 0)
|
||
{
|
||
await WriteEventAsync("error", "请提供要分类的账单ID");
|
||
return;
|
||
}
|
||
|
||
// 获取指定ID的账单
|
||
var records = new List<Entity.TransactionRecord>();
|
||
foreach (var id in request.TransactionIds)
|
||
{
|
||
var record = await transactionRepository.GetByIdAsync(id);
|
||
if (record != null && record.Classify == string.Empty)
|
||
{
|
||
records.Add(record);
|
||
}
|
||
}
|
||
|
||
if (records.Count == 0)
|
||
{
|
||
await WriteEventAsync("error", "找不到指定的账单");
|
||
return;
|
||
}
|
||
|
||
// 获取所有分类
|
||
var categories = await categoryRepository.GetAllAsync();
|
||
|
||
// 构建分类信息
|
||
var categoryInfo = new StringBuilder();
|
||
foreach (var type in new[] { 0, 1, 2 })
|
||
{
|
||
var typeName = GetTypeName((TransactionType)type);
|
||
categoryInfo.AppendLine($"{typeName}: ");
|
||
var categoriesOfType = categories.Where(c => (int)c.Type == type).ToList();
|
||
foreach (var category in categoriesOfType)
|
||
{
|
||
categoryInfo.AppendLine($"- {category.Name}");
|
||
}
|
||
}
|
||
|
||
// 构建账单信息
|
||
var billsInfo = string.Join("\n", records.Select((r, i) =>
|
||
$"{i + 1}. ID={r.Id}, 摘要={r.Reason}, 金额={r.Amount}, 类型={GetTypeName(r.Type)}"));
|
||
|
||
var systemPrompt = $$"""
|
||
你是一个专业的账单分类助手。请根据提供的账单信息和分类列表,为每个账单选择最合适的分类。
|
||
|
||
可用的分类列表:
|
||
{{categoryInfo}}
|
||
|
||
分类规则:
|
||
1. 根据账单的摘要和金额,选择最匹配的分类
|
||
2. 如果无法确定分类,可以选择""其他""
|
||
|
||
请对每个账单进行分类,每次输出一个账单的分类结果,格式如下:
|
||
{"id": 账单ID, "type": 0:支出/1:收入/2:不计入收支(Type为Number枚举值) ,"classify": 分类名称}
|
||
|
||
只输出JSON,不要有其他文字说明。
|
||
""";
|
||
|
||
var userPrompt = $@"请为以下账单进行分类:
|
||
|
||
{billsInfo}
|
||
|
||
请逐个输出分类结果。";
|
||
|
||
// 流式调用AI
|
||
await WriteEventAsync("start", $"开始分类 {records.Count} 条账单");
|
||
|
||
await foreach (var chunk in openAiService.ChatStreamAsync(systemPrompt, userPrompt))
|
||
{
|
||
await WriteEventAsync("data", chunk);
|
||
}
|
||
|
||
await WriteEventAsync("end", "分类完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "智能分类失败");
|
||
await WriteEventAsync("error", $"智能分类失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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;
|
||
}
|
||
var success = await transactionRepository.UpdateAsync(record);
|
||
if (success)
|
||
successCount++;
|
||
else
|
||
failCount++;
|
||
}
|
||
else
|
||
{
|
||
failCount++;
|
||
}
|
||
}
|
||
|
||
return new BaseResponse
|
||
{
|
||
Success = true,
|
||
Message = $"批量更新完成,成功 {successCount} 条,失败 {failCount} 条"
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "批量更新分类失败");
|
||
return BaseResponse.Fail($"批量更新分类失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取按交易摘要分组的统计信息(支持分页)
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<PagedResponse<Repository.ReasonGroupDto>> GetReasonGroupsAsync(
|
||
[FromQuery] int pageIndex = 1,
|
||
[FromQuery] int pageSize = 20)
|
||
{
|
||
try
|
||
{
|
||
var (list, total) = await transactionRepository.GetReasonGroupsAsync(pageIndex, pageSize);
|
||
return new PagedResponse<Repository.ReasonGroupDto>
|
||
{
|
||
Success = true,
|
||
Data = list.ToArray(),
|
||
Total = total
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "获取交易摘要分组失败");
|
||
return PagedResponse<Repository.ReasonGroupDto>.Fail($"获取交易摘要分组失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <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 new BaseResponse<int>
|
||
{
|
||
Success = true,
|
||
Data = count,
|
||
Message = $"成功更新 {count} 条记录"
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "按摘要批量更新分类失败,摘要: {Reason}", dto.Reason);
|
||
return BaseResponse<int>.Fail($"按摘要批量更新分类失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自然语言分析 - 根据用户输入的自然语言查询交易记录并预设分类
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<BaseResponse<NlpAnalysisResult>> NlpAnalysisAsync([FromBody] NlpAnalysisRequest request)
|
||
{
|
||
try
|
||
{
|
||
if (string.IsNullOrWhiteSpace(request.UserInput))
|
||
{
|
||
return BaseResponse<NlpAnalysisResult>.Fail("请输入查询条件");
|
||
}
|
||
|
||
// 获取所有分类
|
||
var categories = await categoryRepository.GetAllAsync();
|
||
var categoryInfo = new StringBuilder();
|
||
foreach (var type in new[] { 0, 1, 2 })
|
||
{
|
||
var typeName = GetTypeName((TransactionType)type);
|
||
categoryInfo.AppendLine($"{typeName}: ");
|
||
var categoriesOfType = categories.Where(c => (int)c.Type == type).ToList();
|
||
foreach (var category in categoriesOfType)
|
||
{
|
||
categoryInfo.AppendLine($"- {category.Name}");
|
||
}
|
||
}
|
||
|
||
var systemPrompt = $$"""
|
||
你是一个专业的交易记录查询助手。用户会用自然语言描述他想要查询和分类的交易记录。
|
||
|
||
可用的分类列表:
|
||
{{categoryInfo}}
|
||
|
||
你需要分析用户的需求,提取以下信息:
|
||
1. 查询SQL, 根据用户的描述生成SQL Where 子句,用于查询交易记录。例如:Reason LIKE '%关键词%'
|
||
Table Schema:
|
||
TransactionRecord (
|
||
Id LONG,
|
||
Reason STRING,
|
||
Amount DECIMAL,
|
||
RefundAmount DECIMAL,
|
||
Balance DECIMAL,
|
||
OccurredAt DATETIME,
|
||
EmailMessageId LONG,
|
||
Type INT,
|
||
Classify STRING,
|
||
ImportNo STRING,
|
||
ImportFrom STRING
|
||
)
|
||
2. 目标交易类型(0:支出, 1:收入, 2:不计入收支)
|
||
3. 目标分类名称(必须从上面的分类列表中选择)
|
||
|
||
请以JSON格式输出,格式如下:
|
||
{
|
||
"sql": "查询SQL",
|
||
"targetType": 交易类型数字,
|
||
"targetClassify": "分类名称"
|
||
}
|
||
|
||
只输出JSON,不要有其他文字说明。
|
||
""";
|
||
|
||
var userPrompt = $"用户输入:{request.UserInput}";
|
||
|
||
// 调用AI分析
|
||
var aiResponse = await openAiService.ChatAsync(systemPrompt, userPrompt);
|
||
logger.LogInformation("NLP分析AI返回结果: {Response}", aiResponse);
|
||
if (string.IsNullOrWhiteSpace(aiResponse))
|
||
{
|
||
return BaseResponse<NlpAnalysisResult>.Fail("AI分析失败,请检查AI配置");
|
||
}
|
||
|
||
// 解析AI返回的JSON
|
||
NlpAnalysisInfo? analysisInfo;
|
||
try
|
||
{
|
||
analysisInfo = JsonSerializer.Deserialize<NlpAnalysisInfo>(aiResponse);
|
||
if (analysisInfo == null)
|
||
{
|
||
return BaseResponse<NlpAnalysisResult>.Fail("AI返回格式错误");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "解析AI返回结果失败,返回内容: {Response}", aiResponse);
|
||
return BaseResponse<NlpAnalysisResult>.Fail($"AI返回格式错误: {ex.Message}");
|
||
}
|
||
|
||
// 根据关键词查询交易记录
|
||
var allRecords = await transactionRepository.QueryBySqlAsync(analysisInfo.Sql);
|
||
logger.LogInformation("NLP分析查询到 {Count} 条记录,SQL: {Sql}", allRecords.Count, analysisInfo.Sql);
|
||
|
||
// 为每条记录预设分类
|
||
var recordsWithClassify = allRecords.Select(r => new TransactionRecordWithClassify
|
||
{
|
||
Id = r.Id,
|
||
Reason = r.Reason ?? string.Empty,
|
||
Amount = r.Amount,
|
||
Balance = r.Balance,
|
||
Card = r.Card ?? string.Empty,
|
||
OccurredAt = r.OccurredAt,
|
||
CreateTime = r.CreateTime,
|
||
ImportFrom = r.ImportFrom ?? string.Empty,
|
||
RefundAmount = r.RefundAmount,
|
||
UpsetedType = analysisInfo.TargetType,
|
||
UpsetedClassify = analysisInfo.TargetClassify,
|
||
TargetType = r.Type,
|
||
TargetClassify = r.Classify ?? string.Empty
|
||
}).ToList();
|
||
|
||
return new BaseResponse<NlpAnalysisResult>
|
||
{
|
||
Success = true,
|
||
Data = new NlpAnalysisResult
|
||
{
|
||
Records = recordsWithClassify,
|
||
TargetType = analysisInfo.TargetType,
|
||
TargetClassify = analysisInfo.TargetClassify,
|
||
SearchKeyword = analysisInfo.Sql
|
||
}
|
||
};
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "NLP分析失败,用户输入: {Input}", request.UserInput);
|
||
return BaseResponse<NlpAnalysisResult>.Fail($"NLP分析失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
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 static string GetTypeName(TransactionType type)
|
||
{
|
||
return type switch
|
||
{
|
||
TransactionType.Expense => "支出",
|
||
TransactionType.Income => "收入",
|
||
TransactionType.None => "不计入收支",
|
||
_ => "未知"
|
||
};
|
||
}
|
||
|
||
|
||
}
|
||
|
||
/// <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
|
||
);
|
||
|
||
/// <summary>
|
||
/// 日历统计响应DTO
|
||
/// </summary>
|
||
public record DailyStatisticsDto(
|
||
string Date,
|
||
int Count,
|
||
decimal Amount
|
||
);
|
||
|
||
/// <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>
|
||
/// NLP分析请求DTO
|
||
/// </summary>
|
||
public record NlpAnalysisRequest(
|
||
string UserInput
|
||
);
|
||
|
||
/// <summary>
|
||
/// NLP分析结果DTO
|
||
/// </summary>
|
||
public record NlpAnalysisResult
|
||
{
|
||
public List<TransactionRecordWithClassify> Records { get; set; } = new();
|
||
public TransactionType TargetType { get; set; }
|
||
public string TargetClassify { get; set; } = string.Empty;
|
||
public string SearchKeyword { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 带分类信息的交易记录DTO
|
||
/// </summary>
|
||
public record TransactionRecordWithClassify
|
||
{
|
||
public long Id { get; set; }
|
||
public string Reason { get; set; } = string.Empty;
|
||
public decimal Amount { get; set; }
|
||
public decimal Balance { get; set; }
|
||
public string Card { get; set; } = string.Empty;
|
||
public DateTime OccurredAt { get; set; }
|
||
public DateTime CreateTime { get; set; }
|
||
public string ImportFrom { get; set; } = string.Empty;
|
||
public decimal RefundAmount { get; set; }
|
||
public TransactionType UpsetedType { get; set; }
|
||
public string UpsetedClassify { get; set; } = string.Empty;
|
||
public TransactionType TargetType { get; set; }
|
||
public string TargetClassify { get; set; } = string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// AI分析信息DTO(内部使用)
|
||
/// </summary>
|
||
public record NlpAnalysisInfo
|
||
{
|
||
[JsonPropertyName("sql")]
|
||
public string Sql { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("targetType")]
|
||
public TransactionType TargetType { get; set; }
|
||
|
||
[JsonPropertyName("targetClassify")]
|
||
public string TargetClassify { get; set; } = string.Empty;
|
||
}
|