All checks were successful
Build and Deploy / deploy (push) Successful in 1m3s
Co-authored-by: Cursor <cursoragent@cursor.com>
337 lines
12 KiB
C#
337 lines
12 KiB
C#
using System.Linq.Expressions;
|
||
using Fake.Application;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Oms.Application.Dtos.Shared;
|
||
using Oms.Application.Dtos.System;
|
||
using Oms.Application.Shared;
|
||
using Oms.Domain.SystemAggregate.Entities;
|
||
using Oms.Domain.SystemAggregate.Repositories;
|
||
|
||
namespace Oms.Application.Services.System;
|
||
|
||
/// <summary>
|
||
/// 字典 API 服务。
|
||
/// </summary>
|
||
[ApiExplorerSettings(GroupName = "OMS-System")]
|
||
public class DictService : ApplicationService
|
||
{
|
||
private IDictRepository DictRepository => LazyServiceProvider.GetRequiredService<IDictRepository>();
|
||
|
||
private IDictTypeRepository DictTypeRepository => LazyServiceProvider.GetRequiredService<IDictTypeRepository>();
|
||
|
||
/// <summary>
|
||
/// 查询字典列表。
|
||
/// </summary>
|
||
/// <param name="input">入参:字典分页查询条件。</param>
|
||
/// <returns>字典分页结果,包含字典列表与总记录数。</returns>
|
||
[HttpGet("/system/dict/list")]
|
||
public async Task<OmsPageResult<DictOutput>> GetListAsync([FromQuery] DictListInput input)
|
||
{
|
||
var predicate = BuildListPredicate(input);
|
||
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
||
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
||
// Fake GetPagedListAsync(sorting) 会丢失 Where:total 正确但 items 返回全量
|
||
var (items, total) = await RepositoryPageHelper.GetPageAsync(
|
||
DictRepository,
|
||
predicate,
|
||
pageIndex,
|
||
pageSize,
|
||
q => q.OrderBy(x => x.DictSort).ThenByDescending(x => x.Id));
|
||
|
||
return new OmsPageResult<DictOutput>
|
||
{
|
||
Items = items.Select(MapDictOutput).ToList(),
|
||
Total = total
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询字典类型列表(含备注与项数)。
|
||
/// </summary>
|
||
/// <param name="keyword">可选:按类型编码或备注模糊搜索。</param>
|
||
/// <returns>字典类型列表。</returns>
|
||
[HttpGet("/system/dict/type/list")]
|
||
public async Task<List<DictTypeOutput>> GetTypeListAsync([FromQuery] string? keyword)
|
||
{
|
||
var kw = keyword?.Trim();
|
||
var metaRows = await DictTypeRepository.GetListAsync();
|
||
var countRows = await DictRepository.GetItemCountByTypeAsync();
|
||
var countMap = countRows
|
||
.Select(x => (Type: x.DictType.Trim(), x.ItemCount))
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.Type))
|
||
.GroupBy(x => x.Type, StringComparer.OrdinalIgnoreCase)
|
||
.ToDictionary(g => g.Key, g => g.Sum(x => x.ItemCount), StringComparer.OrdinalIgnoreCase);
|
||
|
||
var merged = new Dictionary<string, DictTypeOutput>(StringComparer.OrdinalIgnoreCase);
|
||
foreach (var meta in metaRows)
|
||
{
|
||
var type = meta.DictType.Trim();
|
||
if (string.IsNullOrWhiteSpace(type))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
merged[type] = new DictTypeOutput
|
||
{
|
||
Id = meta.Id,
|
||
DictType = type,
|
||
Remark = meta.Remark,
|
||
TypeSort = meta.TypeSort,
|
||
ItemCount = countMap.TryGetValue(type, out var count) ? count : 0
|
||
};
|
||
}
|
||
|
||
foreach (var (dictType, itemCount) in countRows)
|
||
{
|
||
var type = dictType.Trim();
|
||
if (string.IsNullOrWhiteSpace(type) || merged.ContainsKey(type))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
merged[type] = new DictTypeOutput
|
||
{
|
||
DictType = type,
|
||
ItemCount = itemCount
|
||
};
|
||
}
|
||
|
||
IEnumerable<DictTypeOutput> result = merged.Values;
|
||
if (!string.IsNullOrWhiteSpace(kw))
|
||
{
|
||
result = result.Where(x =>
|
||
x.DictType.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
|
||
(!string.IsNullOrWhiteSpace(x.Remark) && x.Remark.Contains(kw, StringComparison.OrdinalIgnoreCase)));
|
||
}
|
||
|
||
return result
|
||
.OrderBy(x => x.TypeSort)
|
||
.ThenBy(x => x.DictType, StringComparer.OrdinalIgnoreCase)
|
||
.ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存字典类型元数据(新增或更新备注)。
|
||
/// </summary>
|
||
/// <param name="input">入参:字典类型元数据。</param>
|
||
/// <returns>无返回体。</returns>
|
||
[HttpPost("/system/dict/type/save")]
|
||
public async Task SaveTypeAsync(DictTypeSaveInput input)
|
||
{
|
||
var dictType = input.DictType?.Trim() ?? string.Empty;
|
||
if (string.IsNullOrWhiteSpace(dictType))
|
||
{
|
||
throw new BusinessException("字典类型不能为空");
|
||
}
|
||
|
||
var remark = string.IsNullOrWhiteSpace(input.Remark) ? null : input.Remark.Trim();
|
||
DictTypeMeta? entity = null;
|
||
if (input.Id.HasValue && input.Id.Value > 0)
|
||
{
|
||
entity = await DictTypeRepository.FirstOrDefaultAsync(x => x.Id == input.Id.Value);
|
||
}
|
||
|
||
entity ??= await DictTypeRepository.GetByDictTypeAsync(dictType);
|
||
// 软删后列表查不到,但 uk_dict_type_code 仍占坑;重建时恢复原行
|
||
entity ??= await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
|
||
if (entity == null)
|
||
{
|
||
await DictTypeRepository.InsertAsync(new DictTypeMeta
|
||
{
|
||
DictType = dictType,
|
||
Remark = remark,
|
||
TypeSort = input.TypeSort
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!string.Equals(entity.DictType, dictType, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var duplicate = await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
|
||
if (duplicate != null && duplicate.Id != entity.Id && !duplicate.IsDeleted)
|
||
{
|
||
throw new BusinessException("字典类型已存在");
|
||
}
|
||
|
||
if (duplicate != null && duplicate.Id != entity.Id && duplicate.IsDeleted)
|
||
{
|
||
throw new BusinessException("字典类型编码与已删除记录冲突,请换编码或联系管理员恢复");
|
||
}
|
||
}
|
||
|
||
entity.IsDeleted = false;
|
||
entity.DictType = dictType;
|
||
entity.Remark = remark;
|
||
entity.TypeSort = input.TypeSort;
|
||
await DictTypeRepository.UpdateAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除字典类型元数据(不删除字典项)。
|
||
/// </summary>
|
||
/// <param name="input">入参:目标主键。</param>
|
||
/// <returns>无返回体。</returns>
|
||
[HttpPost("/system/dict/type/delete")]
|
||
public async Task DeleteTypeAsync(IdInput input)
|
||
{
|
||
var entity = await DictTypeRepository.FirstOrDefaultAsync(x => x.Id == input.Id);
|
||
if (entity == null)
|
||
{
|
||
throw new BusinessException("字典类型不存在");
|
||
}
|
||
|
||
await DictTypeRepository.DeleteAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增字典。
|
||
/// </summary>
|
||
/// <param name="input">入参:字典新增参数。</param>
|
||
/// <returns>无返回体,仅完成新增字典。</returns>
|
||
[HttpPost("/system/dict/create")]
|
||
public async Task CreateAsync(DictCreateInput input)
|
||
{
|
||
var dictType = input.DictType.Trim();
|
||
var dictCode = input.DictCode.Trim();
|
||
var dictName = input.DictName.Trim();
|
||
await ValidateDictInputAsync(dictType, dictCode, dictName, null);
|
||
await EnsureDictTypeMetaAsync(dictType);
|
||
|
||
var deleted = await DictRepository.GetByTypeAndCodeIncludingDeletedAsync(dictType, dictCode);
|
||
if (deleted != null && deleted.IsDeleted)
|
||
{
|
||
deleted.IsDeleted = false;
|
||
deleted.DictSort = input.DictSort;
|
||
deleted.DictName = dictName;
|
||
await DictRepository.UpdateAsync(deleted);
|
||
return;
|
||
}
|
||
|
||
await DictRepository.InsertAsync(new Dict
|
||
{
|
||
DictType = dictType,
|
||
DictSort = input.DictSort,
|
||
DictCode = dictCode,
|
||
DictName = dictName
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新字典。
|
||
/// </summary>
|
||
/// <param name="input">入参:字典更新参数。</param>
|
||
/// <returns>无返回体,仅完成更新字典。</returns>
|
||
[HttpPost("/system/dict/update")]
|
||
public async Task UpdateAsync(DictUpdateInput input)
|
||
{
|
||
var entity = await GetDictOrThrowAsync(input.Id);
|
||
await ValidateDictInputAsync(input.DictType, input.DictCode, input.DictName, entity.Id);
|
||
await EnsureDictTypeMetaAsync(input.DictType.Trim());
|
||
entity.DictType = input.DictType.Trim();
|
||
entity.DictSort = input.DictSort;
|
||
entity.DictCode = input.DictCode.Trim();
|
||
entity.DictName = input.DictName.Trim();
|
||
await DictRepository.UpdateAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除字典。
|
||
/// </summary>
|
||
/// <param name="input">入参:目标主键。</param>
|
||
/// <returns>无返回体,仅完成删除字典。</returns>
|
||
[HttpPost("/system/dict/delete")]
|
||
public async Task DeleteAsync(IdInput input)
|
||
{
|
||
var entity = await GetDictOrThrowAsync(input.Id);
|
||
await DictRepository.DeleteAsync(entity);
|
||
}
|
||
|
||
private static Expression<Func<Dict, bool>> BuildListPredicate(DictListInput input)
|
||
{
|
||
var dictType = input.DictType?.Trim();
|
||
var keyword = input.Keyword?.Trim();
|
||
return x =>
|
||
(string.IsNullOrWhiteSpace(dictType) || x.DictType == dictType) &&
|
||
(string.IsNullOrWhiteSpace(keyword) ||
|
||
x.DictCode.Contains(keyword) ||
|
||
x.DictName.Contains(keyword));
|
||
}
|
||
|
||
private async Task<Dict> GetDictOrThrowAsync(int id)
|
||
{
|
||
var entity = await DictRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||
if (entity == null)
|
||
{
|
||
throw new BusinessException("字典不存在");
|
||
}
|
||
|
||
return entity;
|
||
}
|
||
|
||
private async Task ValidateDictInputAsync(string dictType, string dictCode, string dictName, int? excludeId)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(dictType))
|
||
{
|
||
throw new BusinessException("字典类型不能为空");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(dictCode))
|
||
{
|
||
throw new BusinessException("字典编码不能为空");
|
||
}
|
||
|
||
if (string.IsNullOrWhiteSpace(dictName))
|
||
{
|
||
throw new BusinessException("字典名称不能为空");
|
||
}
|
||
|
||
var existed = await DictRepository.GetByTypeAndCodeAsync(dictType.Trim(), dictCode.Trim());
|
||
if (existed != null && existed.Id != excludeId)
|
||
{
|
||
throw new BusinessException("同类型下字典编码已存在");
|
||
}
|
||
}
|
||
|
||
private static DictOutput MapDictOutput(Dict entity)
|
||
{
|
||
return new DictOutput
|
||
{
|
||
Id = entity.Id,
|
||
DictType = entity.DictType,
|
||
DictSort = entity.DictSort,
|
||
DictCode = entity.DictCode,
|
||
DictName = entity.DictName,
|
||
CreateTime = entity.CreateTime,
|
||
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
|
||
};
|
||
}
|
||
|
||
private async Task EnsureDictTypeMetaAsync(string dictType)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(dictType))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var existed = await DictTypeRepository.GetByDictTypeAsync(dictType);
|
||
if (existed != null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var deleted = await DictTypeRepository.GetByDictTypeIncludingDeletedAsync(dictType);
|
||
if (deleted != null)
|
||
{
|
||
deleted.IsDeleted = false;
|
||
await DictTypeRepository.UpdateAsync(deleted);
|
||
return;
|
||
}
|
||
|
||
await DictTypeRepository.InsertAsync(new DictTypeMeta
|
||
{
|
||
DictType = dictType,
|
||
TypeSort = 0
|
||
});
|
||
}
|
||
}
|