- 新增 Finance 模块应收应付分页查询及订单费用联查 - 为 DataPerm、Xhs 等缺失接口补充中文 summary 接口名称 - 优化微信公众号验签 Swagger 命名并补充路由集成测试 Co-authored-by: Cursor <cursoragent@cursor.com>
313 lines
11 KiB
C#
313 lines
11 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// KPI 分区 API 服务。
|
||
/// </summary>
|
||
[ApiExplorerSettings(GroupName = "OMS-Channel")]
|
||
public class KpiAreaService : ApplicationService
|
||
{
|
||
private IKpiAreaRepository KpiAreaRepository => LazyServiceProvider.GetRequiredService<IKpiAreaRepository>();
|
||
|
||
private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService<ICountryRepository>();
|
||
|
||
/// <summary>
|
||
/// 查询KPI分区列表。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区列表查询条件。</param>
|
||
/// <returns>KPI分区分页结果,包含列表数据和总记录数。</returns>
|
||
[HttpPost("/channel/kpi-area/list")]
|
||
public async Task<OmsPageResult<KpiAreaOutput>> 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<string, bool> { [nameof(KpiArea.UpdateTime)] = false, [nameof(KpiArea.Id)] = false });
|
||
var total = await KpiAreaRepository.CountAsync(predicate);
|
||
return new OmsPageResult<KpiAreaOutput>
|
||
{
|
||
Items = entities.Select(MapAreaOutput).ToList(),
|
||
Total = total
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增KPI分区。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区保存参数。</param>
|
||
/// <returns>无返回体,仅完成新增KPI分区。</returns>
|
||
[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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新KPI分区。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区保存参数。</param>
|
||
/// <returns>无返回体,仅完成更新KPI分区。</returns>
|
||
[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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除KPI分区。
|
||
/// </summary>
|
||
/// <param name="input">入参:目标主键。</param>
|
||
/// <returns>无返回体,仅完成删除KPI分区。</returns>
|
||
[HttpPost("/channel/kpi-area/delete")]
|
||
public async Task DeleteAsync(IdInput input)
|
||
{
|
||
var entity = await GetAreaOrThrowAsync(input.Id);
|
||
await KpiAreaRepository.DeleteAsync(entity);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 查询KPI分区明细列表。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区明细列表查询条件。</param>
|
||
/// <returns>KPI分区明细分页结果,包含列表数据和总记录数。</returns>
|
||
[HttpPost("/channel/kpi-area/info/list")]
|
||
public async Task<OmsPageResult<KpiAreaInfoOutput>> 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<KpiAreaInfoOutput>
|
||
{
|
||
Items = entities.Select(MapInfoOutput).ToList(),
|
||
Total = total
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增KPI分区明细。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区明细保存参数。</param>
|
||
/// <returns>无返回体,仅完成新增KPI分区明细。</returns>
|
||
[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
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新KPI分区明细。
|
||
/// </summary>
|
||
/// <param name="input">入参:KPI分区明细保存参数。</param>
|
||
/// <returns>无返回体,仅完成更新KPI分区明细。</returns>
|
||
[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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除KPI分区明细。
|
||
/// </summary>
|
||
/// <param name="input">入参:目标主键。</param>
|
||
/// <returns>无返回体,仅完成删除KPI分区明细。</returns>
|
||
[HttpPost("/channel/kpi-area/info/delete")]
|
||
public async Task DeleteInfoAsync(IdInput input)
|
||
{
|
||
await GetAreaInfoOrThrowAsync(input.Id);
|
||
await KpiAreaRepository.DeleteInfoAsync(input.Id);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下载 KPI 分区明细导入模板。
|
||
/// </summary>
|
||
[HttpGet("/channel/kpi-area/info/import-template")]
|
||
public async Task<FileContentResult> DownloadInfoImportTemplateAsync([FromQuery] string? areaType)
|
||
{
|
||
var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync();
|
||
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||
{
|
||
FileDownloadName = KpiAreaInfoImportHelper.ResolveTemplateFileName(areaType)
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导入 KPI 分区明细(Excel)。
|
||
/// </summary>
|
||
[HttpPost("/channel/kpi-area/info/import")]
|
||
public async Task<KpiAreaInfoImportOutput> 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<Func<KpiArea, bool>> 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<KpiArea> 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<KpiAreaInfo> 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)
|
||
};
|
||
}
|
||
}
|