admin/src/Oms.Application/Services/Orders/Templates/OrderTemplateHeaderMatcher.cs
zzh ddb8efba15
All checks were successful
Build and Deploy / deploy (push) Successful in 58s
feat: 9610汇总下单关联出口资质,并补齐批量模板字段
下单/导入在 9610Summary 时校验并落库 ExportQualificationId(可回落默认备案),写入 9610 申报企业信息;专线小包模板长宽高改必填并增加资质备案列。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 16:12:39 +08:00

158 lines
5.7 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.Text.RegularExpressions;
using Oms.Application.Services.Orders.Templates;
namespace Oms.Application.Services.Orders.Templates;
/// <summary>
/// 客户 Excel 表头与标准字段智能匹配。
/// </summary>
public static class OrderTemplateHeaderMatcher
{
private static readonly Regex NoisePattern = new(@"[\s\*\(\)()\[\]【】::,,.。/\\\-_]+", RegexOptions.Compiled);
private static readonly Dictionary<string, string> 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<string> sourceHeaders)
{
var fields = OrderTemplateFieldRegistry.GetFields(standardTemplateKey);
var usedColumns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
var mappings = new List<OrderTemplateColumnMappingItem>();
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<string> ReadHeaderRow(Stream stream)
{
stream.Position = 0;
var firstRow = MiniExcelLibs.MiniExcel.Query(stream, useHeaderRow: false)
.Take(1)
.Cast<IDictionary<string, object>>()
.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);
}