diff --git a/Entity/MessageRecord.cs b/Entity/MessageRecord.cs index 66cc2de..6e16607 100644 --- a/Entity/MessageRecord.cs +++ b/Entity/MessageRecord.cs @@ -43,4 +43,9 @@ public class MessageRecord : BaseEntity /// 是否已读 /// public bool IsRead { get; set; } = false; + + /// + /// 跳转URL + /// + public string? Url { get; set; } } diff --git a/Entity/TransactionRecord.cs b/Entity/TransactionRecord.cs index df4e157..96d0662 100644 --- a/Entity/TransactionRecord.cs +++ b/Entity/TransactionRecord.cs @@ -50,6 +50,16 @@ public class TransactionRecord : BaseEntity /// public string Classify { get; set; } = string.Empty; + /// + /// 待确认的分类(AI或规则建议,但尚未正式确认) + /// + public string? UnconfirmedClassify { get; set; } + + /// + /// 待确认的类型 + /// + public TransactionType? UnconfirmedType { get; set; } + /// /// 导入编号 /// diff --git a/Repository/TransactionRecordRepository.cs b/Repository/TransactionRecordRepository.cs index 4711b4f..8d50247 100644 --- a/Repository/TransactionRecordRepository.cs +++ b/Repository/TransactionRecordRepository.cs @@ -178,6 +178,18 @@ public interface ITransactionRecordRepository : IBaseRepository候选交易列表 Task> GetCandidatesForOffsetAsync(long currentId, decimal amount, TransactionType currentType); + /// + /// 获取待确认分类的账单列表 + /// + /// 待确认账单列表 + Task> GetUnconfirmedRecordsAsync(); + + /// + /// 全部确认待确认的分类 + /// + /// 影响行数 + Task ConfirmAllUnconfirmedAsync(); + /// /// 更新分类名称 /// @@ -677,6 +689,25 @@ public class TransactionRecordRepository(IFreeSql freeSql) : BaseRepository a.Classify == oldName && a.Type == type) .ExecuteAffrowsAsync(); } + + public async Task> GetUnconfirmedRecordsAsync() + { + return await FreeSql.Select() + .Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "") + .OrderByDescending(t => t.OccurredAt) + .ToListAsync(); + } + + public async Task ConfirmAllUnconfirmedAsync() + { + return await FreeSql.Update() + .Set(t => t.Classify == t.UnconfirmedClassify) + .Set(t => t.Type == (t.UnconfirmedType ?? t.Type)) + .Set(t => t.UnconfirmedClassify, null) + .Set(t => t.UnconfirmedType, null) + .Where(t => t.UnconfirmedClassify != null && t.UnconfirmedClassify != "") + .ExecuteAffrowsAsync(); + } } /// diff --git a/Service/EmailServices/EmailHandleService.cs b/Service/EmailServices/EmailHandleService.cs index d2a2459..9a2f7cd 100644 --- a/Service/EmailServices/EmailHandleService.cs +++ b/Service/EmailServices/EmailHandleService.cs @@ -102,9 +102,7 @@ public class EmailHandleService( records.Add(record); } - // var analysisResult = await AnalyzeClassifyAsync(records.ToArray()); - // TODO 不应该直接保存 应该保存在备用字段上,前端确认后再更新到正式字段 - + _ = AutoClassifyAsync(records.ToArray()); return allSuccess; } @@ -173,11 +171,34 @@ public class EmailHandleService( records.Add(record); } - _ = await AnalyzeClassifyAsync(records.ToArray()); + _ = AutoClassifyAsync(records.ToArray()); return allSuccess; } + private async Task AutoClassifyAsync(TransactionRecord[] records) + { + await AnalyzeClassifyAsync(records.ToArray()); + + foreach (var record in records) + { + record.UnconfirmedClassify = record.Classify; + record.UnconfirmedType = record.Type; + + record.Classify = ""; // 重置为未分类,等待手动确认 + } + + await trxRepo.UpdateRangeAsync(records); + + // 消息 + await messageRecordService.AddAsync( + "交易记录待确认分类", + $"共有 {records.Length} 条交易记录待确认分类,请点击前往确认。", + MessageType.Url, + "/unconfirmed-classification" + ); + } + private string GetEmailByName(string to) { return emailSettings.Value.SmtpList.FirstOrDefault(s => s.Email == to)?.Name ?? to; diff --git a/Service/MessageRecordService.cs b/Service/MessageRecordService.cs index edd8306..82e3700 100644 --- a/Service/MessageRecordService.cs +++ b/Service/MessageRecordService.cs @@ -5,7 +5,7 @@ public interface IMessageRecordService Task<(IEnumerable List, long Total)> GetPagedListAsync(int pageIndex, int pageSize); Task GetByIdAsync(long id); Task AddAsync(MessageRecord message); - Task AddAsync(string title, string content, MessageType type = MessageType.Text); + Task AddAsync(string title, string content, MessageType type = MessageType.Text, string? url = null); Task MarkAsReadAsync(long id); Task MarkAllAsReadAsync(); Task DeleteAsync(long id); @@ -29,19 +29,20 @@ public class MessageRecordService(IMessageRecordRepository messageRepo, INotific return await messageRepo.AddAsync(message); } - public async Task AddAsync(string title, string content, MessageType type = MessageType.Text) + public async Task AddAsync(string title, string content, MessageType type = MessageType.Text, string? url = null) { var message = new MessageRecord { Title = title, Content = content, MessageType = type, + Url = url, IsRead = false }; var result = await messageRepo.AddAsync(message); if (result) { - await notificationService.SendNotificationAsync(title); + await notificationService.SendNotificationAsync(title, url); } return result; } diff --git a/Web/src/api/transactionRecord.js b/Web/src/api/transactionRecord.js index 07fcd48..14a4e58 100644 --- a/Web/src/api/transactionRecord.js +++ b/Web/src/api/transactionRecord.js @@ -19,6 +19,28 @@ export const getTransactionList = (params = {}) => { }) } +/** + * 获取待确认分类的交易记录列表 + * @returns {Promise<{success: boolean, data: Array}>} + */ +export const getUnconfirmedTransactionList = () => { + return request({ + url: '/TransactionRecord/GetUnconfirmedList', + method: 'get' + }) +} + +/** + * 全部确认待确认的交易分类 + * @returns {Promise<{success: boolean, data: number}>} + */ +export const confirmAllUnconfirmed = () => { + return request({ + url: '/TransactionRecord/ConfirmAllUnconfirmed', + method: 'post' + }) +} + /** * 根据ID获取交易记录详情 * @param {number} id - 交易记录ID diff --git a/Web/src/components/Budget/BudgetCard.vue b/Web/src/components/Budget/BudgetCard.vue index 759f327..7885ae6 100644 --- a/Web/src/components/Budget/BudgetCard.vue +++ b/Web/src/components/Budget/BudgetCard.vue @@ -59,15 +59,14 @@ diff --git a/Web/src/components/SmartClassifyButton.vue b/Web/src/components/SmartClassifyButton.vue index 106b2ba..047248a 100644 --- a/Web/src/components/SmartClassifyButton.vue +++ b/Web/src/components/SmartClassifyButton.vue @@ -333,6 +333,11 @@ const handleSmartClassify = async () => { } } +const removeClassifiedTransaction = (transactionId) => { + // 从已分类结果中移除指定ID的项 + classifiedResults.value = classifiedResults.value.filter(item => item.id !== transactionId) +} + /** * 重置组件状态 */ @@ -344,7 +349,8 @@ const reset = () => { } defineExpose({ - reset + reset, + removeClassifiedTransaction }); diff --git a/Web/src/components/TransactionDetail.vue b/Web/src/components/TransactionDetail.vue index ead8700..a5d1b09 100644 --- a/Web/src/components/TransactionDetail.vue +++ b/Web/src/components/TransactionDetail.vue @@ -11,11 +11,21 @@ - + + - + + + + + + + + + + diff --git a/Web/src/components/TransactionList.vue b/Web/src/components/TransactionList.vue index d051932..cdbcad7 100644 --- a/Web/src/components/TransactionList.vue +++ b/Web/src/components/TransactionList.vue @@ -1,5 +1,5 @@