using System.Text.RegularExpressions; using Oms.Application.Services.Orders.Templates; namespace Oms.Application.Services.Orders.Templates; /// /// 客户 Excel 表头与标准字段智能匹配。 /// public static class OrderTemplateHeaderMatcher { private static readonly Regex NoisePattern = new(@"[\s\*\(\)()\[\]【】::,,.。/\\\-_]+", RegexOptions.Compiled); private static readonly Dictionary Aliases = new(StringComparer.OrdinalIgnoreCase) { ["客户单号"] = "clientNo", ["客户订单号"] = "clientNo", ["外部单号"] = "clientNo", ["运输方式"] = "productCode", ["产品代码"] = "productCode", ["产品编码"] = "productCode", ["货物属性"] = "cargoAttribute", ["出口报关方式"] = "exportCustomsMode", ["出口报关"] = "exportCustomsMode", ["出口资质备案Id"] = "exportQualificationId", ["出口资质备案"] = "exportQualificationId", ["资质备案Id"] = "exportQualificationId", ["仓库"] = "warehouseId", ["仓库ID"] = "warehouseId", ["长cm"] = "length", ["长"] = "length", ["宽cm"] = "width", ["宽"] = "width", ["高cm"] = "height", ["高"] = "height", ["实重kg"] = "actualWeight", ["实重"] = "actualWeight", ["申报币种"] = "declaredCurrency", ["申报金额"] = "declaredSum", ["申报总金额"] = "declaredSum", ["包裹数量"] = "quantity", ["收件人名称"] = "receiver.recipientsName", ["收件姓名"] = "receiver.recipientsName", ["收件人手机"] = "receiver.phone", ["收件国家代码"] = "receiver.countryCode", ["收件国家"] = "receiver.countryCode", ["收件地址1"] = "receiver.streetAddress1", ["收件邮编"] = "receiver.zipCode", ["发件人姓名"] = "sender.senderName", ["申报品名英"] = "items[0].enName", ["品名英"] = "items[0].enName", ["申报品名中"] = "items[0].cnName", ["单价"] = "items[0].unitPrice", ["单价元"] = "items[0].unitPrice", ["单重"] = "items[0].unitWeight", ["单重kg"] = "items[0].unitWeight", ["申报数量"] = "items[0].quantity", ["总金额"] = "items[0].amount", ["总金额元"] = "items[0].amount", ["总重量"] = "items[0].weight", ["总重量kg"] = "items[0].weight", }; public static string? MatchStandardFieldKey(string? sourceHeader, string? templateKey) { var normalized = Normalize(sourceHeader); if (string.IsNullOrWhiteSpace(normalized)) { return null; } if (Aliases.TryGetValue(normalized, out var aliasKey)) { return aliasKey; } var fields = OrderTemplateFieldRegistry.GetFields(templateKey); var byLabel = fields.FirstOrDefault(f => Normalize(f.Label) == normalized); if (byLabel != null) { return byLabel.FieldKey; } return fields.FirstOrDefault(f => normalized.Contains(Normalize(f.Label), StringComparison.OrdinalIgnoreCase) || Normalize(f.Label).Contains(normalized, StringComparison.OrdinalIgnoreCase))?.FieldKey; } public static OrderTemplateColumnMappingDocument BuildSuggestedMapping( string standardTemplateKey, IReadOnlyList sourceHeaders) { var fields = OrderTemplateFieldRegistry.GetFields(standardTemplateKey); var usedColumns = new HashSet(StringComparer.OrdinalIgnoreCase); var mappings = new List(); foreach (var field in fields) { var matched = sourceHeaders.FirstOrDefault(h => { var key = MatchStandardFieldKey(h, standardTemplateKey); return string.Equals(key, field.FieldKey, StringComparison.OrdinalIgnoreCase); }); if (!string.IsNullOrWhiteSpace(matched) && usedColumns.Add(matched)) { mappings.Add(new OrderTemplateColumnMappingItem { StandardFieldKey = field.FieldKey, SourceColumn = matched, Required = field.Required }); } } var mappedColumns = mappings.Select(x => x.SourceColumn).ToHashSet(StringComparer.OrdinalIgnoreCase); return new OrderTemplateColumnMappingDocument { StandardTemplateKey = standardTemplateKey, HeaderRowIndex = 1, DataStartRowIndex = 2, Mappings = mappings, UnmappedSourceColumns = sourceHeaders.Where(h => !mappedColumns.Contains(h)).ToList() }; } public static List ReadHeaderRow(Stream stream) { stream.Position = 0; var firstRow = MiniExcelLibs.MiniExcel.Query(stream, useHeaderRow: false) .Take(1) .Cast>() .FirstOrDefault(); if (firstRow == null) { return []; } return firstRow.Values .Select(v => v?.ToString()?.Trim()) .Where(v => !string.IsNullOrWhiteSpace(v)) .Select(v => v!) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } public static string NormalizeHeader(string? text) { if (string.IsNullOrWhiteSpace(text)) { return string.Empty; } return NoisePattern.Replace(text.Trim(), string.Empty); } private static string Normalize(string? text) => NormalizeHeader(text); }