232 lines
7.2 KiB
C#
232 lines
7.2 KiB
C#
|
|
using Application.Dto.Category;
|
||
|
|
using Service.AI;
|
||
|
|
using System.Text.Json;
|
||
|
|
|
||
|
|
namespace Application;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 交易分类应用服务接口
|
||
|
|
/// </summary>
|
||
|
|
public interface ITransactionCategoryApplication
|
||
|
|
{
|
||
|
|
Task<List<CategoryResponse>> GetListAsync(TransactionType? type = null);
|
||
|
|
Task<CategoryResponse> GetByIdAsync(long id);
|
||
|
|
Task<long> CreateAsync(CreateCategoryRequest request);
|
||
|
|
Task UpdateAsync(UpdateCategoryRequest request);
|
||
|
|
Task DeleteAsync(long id);
|
||
|
|
Task<int> BatchCreateAsync(List<CreateCategoryRequest> requests);
|
||
|
|
Task<string> GenerateIconAsync(GenerateIconRequest request);
|
||
|
|
Task UpdateSelectedIconAsync(UpdateSelectedIconRequest request);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 交易分类应用服务实现
|
||
|
|
/// </summary>
|
||
|
|
public class TransactionCategoryApplication(
|
||
|
|
ITransactionCategoryRepository categoryRepository,
|
||
|
|
ITransactionRecordRepository transactionRepository,
|
||
|
|
IBudgetRepository budgetRepository,
|
||
|
|
ISmartHandleService smartHandleService,
|
||
|
|
ILogger<TransactionCategoryApplication> logger
|
||
|
|
) : ITransactionCategoryApplication
|
||
|
|
{
|
||
|
|
public async Task<List<CategoryResponse>> GetListAsync(TransactionType? type = null)
|
||
|
|
{
|
||
|
|
List<TransactionCategory> categories;
|
||
|
|
if (type.HasValue)
|
||
|
|
{
|
||
|
|
categories = await categoryRepository.GetCategoriesByTypeAsync(type.Value);
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
categories = (await categoryRepository.GetAllAsync()).ToList();
|
||
|
|
}
|
||
|
|
|
||
|
|
return categories.Select(MapToResponse).ToList();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<CategoryResponse> GetByIdAsync(long id)
|
||
|
|
{
|
||
|
|
var category = await categoryRepository.GetByIdAsync(id);
|
||
|
|
if (category == null)
|
||
|
|
{
|
||
|
|
throw new NotFoundException("分类不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
return MapToResponse(category);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<long> CreateAsync(CreateCategoryRequest request)
|
||
|
|
{
|
||
|
|
// 检查同名分类
|
||
|
|
var existing = await categoryRepository.GetByNameAndTypeAsync(request.Name, request.Type);
|
||
|
|
if (existing != null)
|
||
|
|
{
|
||
|
|
throw new ValidationException("已存在相同名称的分类");
|
||
|
|
}
|
||
|
|
|
||
|
|
var category = new TransactionCategory
|
||
|
|
{
|
||
|
|
Name = request.Name,
|
||
|
|
Type = request.Type
|
||
|
|
};
|
||
|
|
|
||
|
|
var result = await categoryRepository.AddAsync(category);
|
||
|
|
if (!result)
|
||
|
|
{
|
||
|
|
throw new BusinessException("创建分类失败");
|
||
|
|
}
|
||
|
|
|
||
|
|
return category.Id;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task UpdateAsync(UpdateCategoryRequest request)
|
||
|
|
{
|
||
|
|
var category = await categoryRepository.GetByIdAsync(request.Id);
|
||
|
|
if (category == null)
|
||
|
|
{
|
||
|
|
throw new NotFoundException("分类不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 如果修改了名称,检查同名
|
||
|
|
if (category.Name != request.Name)
|
||
|
|
{
|
||
|
|
var existing = await categoryRepository.GetByNameAndTypeAsync(request.Name, category.Type);
|
||
|
|
if (existing != null && existing.Id != request.Id)
|
||
|
|
{
|
||
|
|
throw new ValidationException("已存在相同名称的分类");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 同步更新交易记录中的分类名称
|
||
|
|
await transactionRepository.UpdateCategoryNameAsync(category.Name, request.Name, category.Type);
|
||
|
|
await budgetRepository.UpdateBudgetCategoryNameAsync(category.Name, request.Name, category.Type);
|
||
|
|
}
|
||
|
|
|
||
|
|
category.Name = request.Name;
|
||
|
|
category.UpdateTime = DateTime.Now;
|
||
|
|
|
||
|
|
var success = await categoryRepository.UpdateAsync(category);
|
||
|
|
if (!success)
|
||
|
|
{
|
||
|
|
throw new BusinessException("更新分类失败");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task DeleteAsync(long id)
|
||
|
|
{
|
||
|
|
// 检查是否被使用
|
||
|
|
var inUse = await categoryRepository.IsCategoryInUseAsync(id);
|
||
|
|
if (inUse)
|
||
|
|
{
|
||
|
|
throw new ValidationException("该分类已被使用,无法删除");
|
||
|
|
}
|
||
|
|
|
||
|
|
var success = await categoryRepository.DeleteAsync(id);
|
||
|
|
if (!success)
|
||
|
|
{
|
||
|
|
throw new BusinessException("删除分类失败,分类不存在");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<int> BatchCreateAsync(List<CreateCategoryRequest> requests)
|
||
|
|
{
|
||
|
|
var categories = requests.Select(r => new TransactionCategory
|
||
|
|
{
|
||
|
|
Name = r.Name,
|
||
|
|
Type = r.Type
|
||
|
|
}).ToList();
|
||
|
|
|
||
|
|
var result = await categoryRepository.AddRangeAsync(categories);
|
||
|
|
if (!result)
|
||
|
|
{
|
||
|
|
throw new BusinessException("批量创建分类失败");
|
||
|
|
}
|
||
|
|
|
||
|
|
return categories.Count;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task<string> GenerateIconAsync(GenerateIconRequest request)
|
||
|
|
{
|
||
|
|
var category = await categoryRepository.GetByIdAsync(request.CategoryId);
|
||
|
|
if (category == null)
|
||
|
|
{
|
||
|
|
throw new NotFoundException("分类不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 使用 SmartHandleService 统一封装的图标生成方法
|
||
|
|
var svg = await smartHandleService.GenerateSingleCategoryIconAsync(category.Name, category.Type);
|
||
|
|
if (string.IsNullOrWhiteSpace(svg))
|
||
|
|
{
|
||
|
|
throw new BusinessException("AI生成图标失败");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 解析现有图标数组
|
||
|
|
var icons = string.IsNullOrWhiteSpace(category.Icon)
|
||
|
|
? new List<string>()
|
||
|
|
: JsonSerializer.Deserialize<List<string>>(category.Icon) ?? new List<string>();
|
||
|
|
|
||
|
|
// 添加新图标
|
||
|
|
icons.Add(svg);
|
||
|
|
|
||
|
|
// 更新数据库
|
||
|
|
category.Icon = JsonSerializer.Serialize(icons);
|
||
|
|
category.UpdateTime = DateTime.Now;
|
||
|
|
|
||
|
|
var success = await categoryRepository.UpdateAsync(category);
|
||
|
|
if (!success)
|
||
|
|
{
|
||
|
|
throw new BusinessException("更新分类图标失败");
|
||
|
|
}
|
||
|
|
|
||
|
|
return svg;
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task UpdateSelectedIconAsync(UpdateSelectedIconRequest request)
|
||
|
|
{
|
||
|
|
var category = await categoryRepository.GetByIdAsync(request.CategoryId);
|
||
|
|
if (category == null)
|
||
|
|
{
|
||
|
|
throw new NotFoundException("分类不存在");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 验证索引有效性
|
||
|
|
if (string.IsNullOrWhiteSpace(category.Icon))
|
||
|
|
{
|
||
|
|
throw new ValidationException("该分类没有可用图标");
|
||
|
|
}
|
||
|
|
|
||
|
|
var icons = JsonSerializer.Deserialize<List<string>>(category.Icon);
|
||
|
|
if (icons == null || request.SelectedIndex < 0 || request.SelectedIndex >= icons.Count)
|
||
|
|
{
|
||
|
|
throw new ValidationException("无效的图标索引");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 将选中的图标移到数组第一位
|
||
|
|
var selectedIcon = icons[request.SelectedIndex];
|
||
|
|
icons.RemoveAt(request.SelectedIndex);
|
||
|
|
icons.Insert(0, selectedIcon);
|
||
|
|
|
||
|
|
category.Icon = JsonSerializer.Serialize(icons);
|
||
|
|
category.UpdateTime = DateTime.Now;
|
||
|
|
|
||
|
|
var success = await categoryRepository.UpdateAsync(category);
|
||
|
|
if (!success)
|
||
|
|
{
|
||
|
|
throw new BusinessException("更新图标失败");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static CategoryResponse MapToResponse(TransactionCategory category)
|
||
|
|
{
|
||
|
|
return new CategoryResponse
|
||
|
|
{
|
||
|
|
Id = category.Id,
|
||
|
|
Name = category.Name,
|
||
|
|
Type = category.Type,
|
||
|
|
Icon = category.Icon,
|
||
|
|
CreateTime = category.CreateTime,
|
||
|
|
UpdateTime = category.UpdateTime
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|