using System.Linq.Expressions; using Fake.Application; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.Channels; using Oms.Application.Dtos.Shared; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.ChannelsAggregate.Repositories; using Oms.Domain.SystemAggregate.Repositories; namespace Oms.Application.Services.Channels; /// /// KPI 分区 API 服务。 /// [ApiExplorerSettings(GroupName = "OMS-Channel")] public class KpiAreaService : ApplicationService { private IKpiAreaRepository KpiAreaRepository => LazyServiceProvider.GetRequiredService(); private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService(); /// /// 查询KPI分区列表。 /// /// 入参:KPI分区列表查询条件。 /// KPI分区分页结果,包含列表数据和总记录数。 [HttpPost("/channel/kpi-area/list")] public async Task> GetListAsync(KpiAreaListInput input) { var predicate = BuildAreaPredicate(input); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var entities = await KpiAreaRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(KpiArea.UpdateTime)] = false, [nameof(KpiArea.Id)] = false }); var total = await KpiAreaRepository.CountAsync(predicate); return new OmsPageResult { Items = entities.Select(MapAreaOutput).ToList(), Total = total }; } /// /// 新增KPI分区。 /// /// 入参:KPI分区保存参数。 /// 无返回体,仅完成新增KPI分区。 [HttpPost("/channel/kpi-area/create")] public async Task CreateAsync(KpiAreaSaveInput input) { await ValidateAreaNameAsync(input.AppKey, input.AreaType, input.Name, null); var entity = new KpiArea { AppKey = input.AppKey, AreaType = input.AreaType, Name = input.Name, SystemName = input.SystemName }; await KpiAreaRepository.InsertAsync(entity); } /// /// 更新KPI分区。 /// /// 入参:KPI分区保存参数。 /// 无返回体,仅完成更新KPI分区。 [HttpPost("/channel/kpi-area/update")] public async Task UpdateAsync(KpiAreaSaveInput input) { var entity = await GetAreaOrThrowAsync(input.Id); await ValidateAreaNameAsync(input.AppKey, input.AreaType, input.Name, entity.Id); entity.AppKey = input.AppKey; entity.AreaType = input.AreaType; entity.Name = input.Name; entity.SystemName = input.SystemName; await KpiAreaRepository.UpdateAsync(entity); } /// /// 删除KPI分区。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除KPI分区。 [HttpPost("/channel/kpi-area/delete")] public async Task DeleteAsync(IdInput input) { var entity = await GetAreaOrThrowAsync(input.Id); await KpiAreaRepository.DeleteAsync(entity); } /// /// 查询KPI分区明细列表。 /// /// 入参:KPI分区明细列表查询条件。 /// KPI分区明细分页结果,包含列表数据和总记录数。 [HttpPost("/channel/kpi-area/info/list")] public async Task> GetInfoListAsync(KpiAreaInfoListInput input) { await GetAreaOrThrowAsync(input.AreaId); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var entities = await KpiAreaRepository.GetInfosAsync(input.AreaId, pageIndex, pageSize); var total = await KpiAreaRepository.CountInfosAsync(input.AreaId); return new OmsPageResult { Items = entities.Select(MapInfoOutput).ToList(), Total = total }; } /// /// 新增KPI分区明细。 /// /// 入参:KPI分区明细保存参数。 /// 无返回体,仅完成新增KPI分区明细。 [HttpPost("/channel/kpi-area/info/add")] public async Task AddInfoAsync(KpiAreaInfoSaveInput input) { await GetAreaOrThrowAsync(input.AreaId); await KpiAreaRepository.InsertInfoAsync(new KpiAreaInfo { AreaId = input.AreaId, CountryCode = input.CountryCode, CountryName = input.CountryName, Province = input.Province, City = input.City, LeftPostcode = input.LeftPostcode, RightPostcode = input.RightPostcode }); } /// /// 更新KPI分区明细。 /// /// 入参:KPI分区明细保存参数。 /// 无返回体,仅完成更新KPI分区明细。 [HttpPost("/channel/kpi-area/info/update")] public async Task UpdateInfoAsync(KpiAreaInfoSaveInput input) { var entity = await GetAreaInfoOrThrowAsync(input.Id); await GetAreaOrThrowAsync(input.AreaId); entity.AreaId = input.AreaId; entity.CountryCode = input.CountryCode; entity.CountryName = input.CountryName; entity.Province = input.Province; entity.City = input.City; entity.LeftPostcode = input.LeftPostcode; entity.RightPostcode = input.RightPostcode; await KpiAreaRepository.UpdateInfoAsync(entity); } /// /// 删除KPI分区明细。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除KPI分区明细。 [HttpPost("/channel/kpi-area/info/delete")] public async Task DeleteInfoAsync(IdInput input) { await GetAreaInfoOrThrowAsync(input.Id); await KpiAreaRepository.DeleteInfoAsync(input.Id); } /// /// 下载 KPI 分区明细导入模板。 /// [HttpGet("/channel/kpi-area/info/import-template")] public async Task DownloadInfoImportTemplateAsync([FromQuery] string? areaType) { var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync(); return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") { FileDownloadName = KpiAreaInfoImportHelper.ResolveTemplateFileName(areaType) }; } /// /// 导入 KPI 分区明细(Excel)。 /// [HttpPost("/channel/kpi-area/info/import")] public async Task ImportInfoAsync([FromForm] KpiAreaInfoImportInput input) { if (input.File == null || input.File.Length == 0) { throw new BusinessException("请上传 Excel 文件"); } await GetAreaOrThrowAsync(input.AreaId); await using var stream = input.File.OpenReadStream(); var parsedRows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream); if (parsedRows.Count == 0) { throw new BusinessException("Excel 中无有效数据行"); } var countries = await CountryRepository.GetListAsync(); var countriesByCode = countries .Where(x => !string.IsNullOrWhiteSpace(x.CountryCode)) .GroupBy(x => x.CountryCode.Trim(), StringComparer.OrdinalIgnoreCase) .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); var output = new KpiAreaInfoImportOutput(); foreach (var (rowNumber, entity) in parsedRows) { try { KpiAreaInfoImportHelper.PrepareEntityForImport(entity, countriesByCode); entity.AreaId = input.AreaId; await KpiAreaRepository.InsertInfoAsync(entity); output.SuccessCount++; } catch (Exception ex) { output.FailCount++; output.Failures.Add(new KpiAreaInfoImportFailRow { RowNumber = rowNumber, Reason = ex.Message }); } } return output; } private static Expression> BuildAreaPredicate(KpiAreaListInput input) { var appKey = input.AppKey?.Trim(); var areaType = input.AreaType?.Trim(); return entity => (string.IsNullOrWhiteSpace(appKey) || entity.AppKey == appKey) && (string.IsNullOrWhiteSpace(areaType) || entity.AreaType == areaType); } private async Task ValidateAreaNameAsync(string appKey, string areaType, string name, int? id) { var existed = await KpiAreaRepository.GetByAppKeyTypeAndNameAsync(appKey, areaType, name); if (existed != null && existed.Id != id) { throw new BusinessException("分区名称已存在"); } } private async Task GetAreaOrThrowAsync(int? id) { if (!id.HasValue || id.Value <= 0) { throw new BusinessException("分区不存在"); } var entity = await KpiAreaRepository.FirstOrDefaultAsync(x => x.Id == id.Value); if (entity == null) { throw new BusinessException("分区不存在"); } return entity; } private async Task GetAreaInfoOrThrowAsync(int? id) { if (!id.HasValue || id.Value <= 0) { throw new BusinessException("分区明细不存在"); } var entity = await KpiAreaRepository.GetInfoByIdAsync(id.Value); if (entity == null) { throw new BusinessException("分区明细不存在"); } return entity; } private static KpiAreaOutput MapAreaOutput(KpiArea entity) { return new KpiAreaOutput { Id = entity.Id, AppKey = entity.AppKey, AreaType = entity.AreaType, Name = entity.Name, SystemName = entity.SystemName, CreateTime = entity.CreateTime, UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime) }; } private static KpiAreaInfoOutput MapInfoOutput(KpiAreaInfo entity) { return new KpiAreaInfoOutput { Id = entity.Id, AreaId = entity.AreaId, CountryCode = entity.CountryCode, CountryName = entity.CountryName, Province = entity.Province, City = entity.City, LeftPostcode = entity.LeftPostcode, RightPostcode = entity.RightPostcode, CreateTime = entity.CreateTime, UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime) }; } }