admin/src/Oms.Application/Services/Channels/KpiAreaService.cs
zzh 0a5bd75618 feat: 新增财务应收应付接口并补充 Swagger 接口名称
- 新增 Finance 模块应收应付分页查询及订单费用联查

- 为 DataPerm、Xhs 等缺失接口补充中文 summary 接口名称

- 优化微信公众号验签 Swagger 命名并补充路由集成测试

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-04 12:15:15 +08:00

313 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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