diff --git a/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs b/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs index 2ebfabf..d61f58f 100644 --- a/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs +++ b/src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs @@ -11,6 +11,9 @@ namespace Oms.Application.Shared; /// public static class KpiAreaInfoImportHelper { + public const string TemplateSheetName = "导入模板"; + public const string EnumSheetName = "枚举值"; + private static readonly Regex ChinesePattern = new(@"[\u4E00-\u9FA5]", RegexOptions.Compiled); private static readonly Dictionary HeaderAliases = new(StringComparer.OrdinalIgnoreCase) @@ -48,12 +51,11 @@ public static class KpiAreaInfoImportHelper public static async Task BuildTemplateBytesAsync() { - var rows = new List> + var templateRows = new List> { new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "zip", ["匹配方式"] = "range", ["州/省"] = "", @@ -64,7 +66,6 @@ public static class KpiAreaInfoImportHelper new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "zip", ["匹配方式"] = "prefix", ["州/省"] = "", @@ -75,7 +76,6 @@ public static class KpiAreaInfoImportHelper new() { ["国家代码"] = "US", - ["国家名称"] = "美国", ["匹配维度"] = "state", ["匹配方式"] = "exact", ["州/省"] = "CA", @@ -85,11 +85,44 @@ public static class KpiAreaInfoImportHelper } }; + var enumRows = new List> + { + new() { ["枚举类型"] = "匹配维度", ["编码"] = "country", ["名称"] = "仅国家" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "zip", ["名称"] = "邮编" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "state", ["名称"] = "州/省" }, + new() { ["枚举类型"] = "匹配维度", ["编码"] = "city", ["名称"] = "城市" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "prefix", ["名称"] = "前缀" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "exact", ["名称"] = "精确" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "range", ["名称"] = "区间" }, + new() { ["枚举类型"] = "匹配方式", ["编码"] = "all", ["名称"] = "不限" } + }; + + var sheets = new Dictionary + { + [TemplateSheetName] = templateRows, + [EnumSheetName] = enumRows + }; + await using var stream = new MemoryStream(); - await MiniExcel.SaveAsAsync(stream, rows); + await MiniExcel.SaveAsAsync(stream, sheets); return stream.ToArray(); } + internal static string? ResolveImportSheetName(IEnumerable sheetNames) + { + var names = sheetNames.Where(x => !string.IsNullOrWhiteSpace(x)).ToList(); + if (names.Count == 0) + { + return null; + } + + return names.FirstOrDefault(x => + string.Equals(x, TemplateSheetName, StringComparison.OrdinalIgnoreCase)) + ?? names.FirstOrDefault(x => + !string.Equals(x, EnumSheetName, StringComparison.OrdinalIgnoreCase)) + ?? names[0]; + } + public static string ResolveTemplateFileName(string? areaType) => areaType?.Trim().ToLowerInvariant() switch { @@ -101,7 +134,20 @@ public static class KpiAreaInfoImportHelper public static async Task> ParseImportRowsAsync(Stream fileStream) { - var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true); + if (fileStream.CanSeek) + { + fileStream.Position = 0; + } + + var sheetNames = MiniExcel.GetSheetNames(fileStream); + var sheetName = ResolveImportSheetName(sheetNames); + + if (fileStream.CanSeek) + { + fileStream.Position = 0; + } + + var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true, sheetName: sheetName); var result = new List<(int RowNumber, KpiAreaInfo Entity)>(); var rowNumber = 1; diff --git a/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs b/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs index 2f62e90..03cdf8c 100644 --- a/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs +++ b/src/Oms.Tests/KpiAreaInfoImportHelperTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using MiniExcelLibs; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.SystemAggregate.Entities; @@ -7,6 +8,63 @@ namespace Oms.Tests; public class KpiAreaInfoImportHelperTests { + [Fact] + public async Task BuildTemplateBytesAsync_ShouldUseNamedSheetsWithoutCountryName() + { + var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync(); + await using var stream = new MemoryStream(bytes); + + var sheetNames = MiniExcel.GetSheetNames(stream); + sheetNames.Should().Equal( + KpiAreaInfoImportHelper.TemplateSheetName, + KpiAreaInfoImportHelper.EnumSheetName); + + stream.Position = 0; + var templateRows = (await MiniExcel.QueryAsync( + stream, + useHeaderRow: true, + sheetName: KpiAreaInfoImportHelper.TemplateSheetName)) + .Cast>() + .ToList(); + templateRows.Should().NotBeEmpty(); + templateRows[0].Keys.Should().NotContain(key => key.Contains("国家名称")); + templateRows[0].Should().ContainKey("国家代码"); + templateRows[0].Should().ContainKey("匹配维度"); + templateRows[0].Should().ContainKey("匹配方式"); + + stream.Position = 0; + var enumRows = (await MiniExcel.QueryAsync( + stream, + useHeaderRow: true, + sheetName: KpiAreaInfoImportHelper.EnumSheetName)) + .Cast>() + .ToList(); + enumRows.Should().HaveCount(8); + enumRows.Should().Contain(row => + Convert.ToString(row["枚举类型"]) == "匹配维度" + && Convert.ToString(row["编码"]) == "zip" + && Convert.ToString(row["名称"]) == "邮编"); + enumRows.Should().Contain(row => + Convert.ToString(row["枚举类型"]) == "匹配方式" + && Convert.ToString(row["编码"]) == "range" + && Convert.ToString(row["名称"]) == "区间"); + } + + [Fact] + public async Task ParseImportRowsAsync_ShouldReadTemplateSheetOnly() + { + var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync(); + await using var stream = new MemoryStream(bytes); + + var rows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream); + + rows.Should().HaveCount(3); + rows[0].Entity.CountryCode.Should().Be("US"); + rows[0].Entity.CountryName.Should().BeNull(); + rows[0].Entity.MatchDim.Should().Be("zip"); + rows[0].Entity.MatchMode.Should().Be("range"); + } + [Fact] public void PrepareEntityForImport_ValidRow_ShouldFillCountryName() {