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)
};
}
}