feat: KPI 分区导入支持增量/全量,删除分区级联明细

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-10-07 17:34:31 +08:00
parent 35156eedf4
commit ae4e733a71
4 changed files with 45 additions and 3 deletions

View File

@ -12,8 +12,19 @@ public class KpiAreaInfoImportInput
/// </summary> /// </summary>
public int AreaId { get; set; } public int AreaId { get; set; }
/// <summary>
/// 导入模式:incremental=增量(跳过重复);full=全量(先清空本分区明细再导入)。
/// </summary>
public string ImportMode { get; set; } = ImportModes.Incremental;
/// <summary> /// <summary>
/// Excel 文件。 /// Excel 文件。
/// </summary> /// </summary>
public IFormFile File { get; set; } = null!; public IFormFile File { get; set; } = null!;
public static class ImportModes
{
public const string Incremental = "incremental";
public const string Full = "full";
}
} }

View File

@ -91,6 +91,7 @@ public class KpiAreaService : ApplicationService
public async Task DeleteAsync(IdInput input) public async Task DeleteAsync(IdInput input)
{ {
var entity = await GetAreaOrThrowAsync(input.Id); var entity = await GetAreaOrThrowAsync(input.Id);
await KpiAreaRepository.DeleteInfosByAreaIdAsync(entity.Id);
await KpiAreaRepository.DeleteAsync(entity); await KpiAreaRepository.DeleteAsync(entity);
} }
@ -203,6 +204,7 @@ public class KpiAreaService : ApplicationService
} }
await GetAreaOrThrowAsync(input.AreaId); await GetAreaOrThrowAsync(input.AreaId);
var importMode = NormalizeImportMode(input.ImportMode);
await using var stream = input.File.OpenReadStream(); await using var stream = input.File.OpenReadStream();
var parsedRows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream); var parsedRows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream);
@ -218,10 +220,17 @@ public class KpiAreaService : ApplicationService
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase); .ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
var existingKeys = new HashSet<string>(StringComparer.Ordinal); var existingKeys = new HashSet<string>(StringComparer.Ordinal);
if (importMode == KpiAreaInfoImportInput.ImportModes.Full)
{
await KpiAreaRepository.DeleteInfosByAreaIdAsync(input.AreaId);
}
else
{
foreach (var item in await KpiAreaRepository.GetAllInfosByAreaIdAsync(input.AreaId)) foreach (var item in await KpiAreaRepository.GetAllInfosByAreaIdAsync(input.AreaId))
{ {
TryAddDuplicateKey(existingKeys, item); TryAddDuplicateKey(existingKeys, item);
} }
}
var output = new KpiAreaInfoImportOutput(); var output = new KpiAreaInfoImportOutput();
foreach (var (rowNumber, entity) in parsedRows) foreach (var (rowNumber, entity) in parsedRows)
@ -236,7 +245,9 @@ public class KpiAreaService : ApplicationService
output.Failures.Add(new KpiAreaInfoImportFailRow output.Failures.Add(new KpiAreaInfoImportFailRow
{ {
RowNumber = rowNumber, RowNumber = rowNumber,
Reason = "与已有规则重复,已跳过" Reason = importMode == KpiAreaInfoImportInput.ImportModes.Full
? "文件内规则重复,已跳过"
: "与已有规则重复,已跳过"
}); });
continue; continue;
} }
@ -258,6 +269,18 @@ public class KpiAreaService : ApplicationService
return output; return output;
} }
private static string NormalizeImportMode(string? mode)
{
if (string.Equals(mode, KpiAreaInfoImportInput.ImportModes.Full, StringComparison.OrdinalIgnoreCase)
|| string.Equals(mode, "全量", StringComparison.OrdinalIgnoreCase)
|| string.Equals(mode, "replace", StringComparison.OrdinalIgnoreCase))
{
return KpiAreaInfoImportInput.ImportModes.Full;
}
return KpiAreaInfoImportInput.ImportModes.Incremental;
}
private static Expression<Func<KpiArea, bool>> BuildAreaPredicate(KpiAreaListInput input) private static Expression<Func<KpiArea, bool>> BuildAreaPredicate(KpiAreaListInput input)
{ {
var appKey = input.AppKey?.Trim(); var appKey = input.AppKey?.Trim();

View File

@ -21,5 +21,7 @@ public interface IKpiAreaRepository : IRepository<KpiArea>, IKpiAreaPublishRepos
Task DeleteInfoAsync(int id, CancellationToken cancellationToken = default); Task DeleteInfoAsync(int id, CancellationToken cancellationToken = default);
Task DeleteInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default);
Task<List<KpiAreaInfo>> GetAllInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default); Task<List<KpiAreaInfo>> GetAllInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default);
} }

View File

@ -57,6 +57,12 @@ public class KpiAreaRepository : OmsSqlSugarRepository<KpiArea>, IKpiAreaReposit
await db.Deleteable<KpiAreaInfo>().Where(x => x.Id == id).ExecuteCommandAsync(cancellationToken); await db.Deleteable<KpiAreaInfo>().Where(x => x.Id == id).ExecuteCommandAsync(cancellationToken);
} }
public async Task DeleteInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
await db.Deleteable<KpiAreaInfo>().Where(x => x.AreaId == areaId).ExecuteCommandAsync(cancellationToken);
}
public async Task<List<KpiAreaInfo>> GetAllInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default) public async Task<List<KpiAreaInfo>> GetAllInfosByAreaIdAsync(int areaId, CancellationToken cancellationToken = default)
{ {
var db = await GetDbContextAsync(cancellationToken); var db = await GetDbContextAsync(cancellationToken);