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; /// /// 字典 API 服务。 /// [ApiExplorerSettings(GroupName = "OMS-System")] public class DictService : ApplicationService { private IDictRepository DictRepository => LazyServiceProvider.GetRequiredService(); private IDictTypeRepository DictTypeRepository => LazyServiceProvider.GetRequiredService(); /// /// 查询字典列表。 /// /// 入参:字典分页查询条件。 /// 字典分页结果,包含字典列表与总记录数。 [HttpGet("/system/dict/list")] public async Task> 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 { Items = items.Select(MapDictOutput).ToList(), Total = total }; } /// /// 查询字典类型列表(含备注与项数)。 /// /// 可选:按类型编码或备注模糊搜索。 /// 字典类型列表。 [HttpGet("/system/dict/type/list")] public async Task> 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(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 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(); } /// /// 保存字典类型元数据(新增或更新备注)。 /// /// 入参:字典类型元数据。 /// 无返回体。 [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); } /// /// 删除字典类型元数据(不删除字典项)。 /// /// 入参:目标主键。 /// 无返回体。 [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); } /// /// 新增字典。 /// /// 入参:字典新增参数。 /// 无返回体,仅完成新增字典。 [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 }); } /// /// 更新字典。 /// /// 入参:字典更新参数。 /// 无返回体,仅完成更新字典。 [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); } /// /// 删除字典。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除字典。 [HttpPost("/system/dict/delete")] public async Task DeleteAsync(IdInput input) { var entity = await GetDictOrThrowAsync(input.Id); await DictRepository.DeleteAsync(entity); } private static Expression> 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 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 }); } }