using System.Text.Json;
using Admin.NET.Plugin.AiDOP.Entity.DataPlatform;
namespace Admin.NET.Plugin.AiDOP.DataPlatform.Inbound;
///
/// mdp_field_map 两层映射:精确 access_key 覆盖 * 兜底;无映射行则恒等。
/// 与 PULL 的 mdp_field_mapping 不是同一张表。
///
public sealed class MdpInboundFieldMapper : ITransient
{
private static readonly IReadOnlyDictionary IdentityFields =
new Dictionary(StringComparer.OrdinalIgnoreCase)
{
["S1_SALES_ORDER_ENTRY"] =
[
Req("bill_no"), Req("entry_seq"), Req("seorder_id"), Req("qty"),
Opt("item_number"), Opt("item_name"), Opt("specification"), Opt("unit"),
Opt("map_number"), Opt("map_name"), Opt("bom_number"),
Opt("deliver_notice_count"), Opt("deliver_count"),
Opt("price"), Opt("tax_price"), Opt("amount"), Opt("total_amount"),
Opt("plan_date"), Opt("date"), Opt("sys_capacity_date"), Opt("sys_material_date"),
Opt("planner_no"), Opt("planner_name"), Opt("custom_order_bill_no"),
Opt("progress"), Opt("urgent")
],
["MDM_ITEM"] =
[
Req("ItemNum"), Req("Descr"),
Opt("Drawing"), Opt("UM"), Opt("ItemType"), Opt("Status"), Opt("Domain")
],
["S5_INVENTORY_TXN"] =
[
Req("Id"), Opt("idid"), Opt("code"), Opt("sl"), Opt("slzx"),
Opt("shyn"), Opt("shtime"), Opt("date0"), Opt("lynoid"),
Opt("gdyn"), Opt("gdtime"), Opt("rwnoid"), Opt("jhdate"), Opt("addtime")
],
["S5_INVENTORY_OPENING_BALANCE"] =
[
Req("Id"), Opt("idid"), Opt("code"), Opt("sl"), Opt("slzx"),
Opt("shyn"), Opt("shtime"), Opt("date0")
],
["S7_FINISHED_ONHAND"] =
[
Req("Id"), Opt("code"), Opt("sl"), Opt("date0"), Opt("location")
],
["S7_FINISHED_OPENING_BALANCE"] =
[
Req("Id"), Opt("code"), Opt("sl"), Opt("date0"), Opt("location")
],
["MDM_EMPLOYEE_HEADCOUNT"] =
[
Req("Domain"), Req("Employee"),
Opt("Name"), Opt("Department")
]
};
private static readonly MdpInboundSchemaField[] CommonContractFields =
[
Req("sourceUpdatedAt"),
Opt("sourceVersion"),
Opt("op")
];
private readonly ISqlSugarClient _db;
public MdpInboundFieldMapper(ISqlSugarClient db) => _db = db;
public async Task> LoadAsync(
long tenantId, string entityCode, string accessKey, CancellationToken ct)
{
var code = (entityCode ?? string.Empty).Trim().ToUpperInvariant();
var rows = await _db.Queryable()
.Where(m => m.TenantId == tenantId && m.Status == 1)
.Where(m => m.AccessKey == accessKey || m.AccessKey == "*")
.Where("UPPER(entity_code) = @code", new SugarParameter("@code", code))
.ToListAsync(ct);
var merged = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var row in rows.Where(r => r.AccessKey == "*"))
merged[row.ContractField] = row;
foreach (var row in rows.Where(r => r.AccessKey != "*"))
merged[row.ContractField] = row;
return merged.Values.ToList();
}
/// 把对外规范字段改写成 raw 键;未登记字段原样保留。输出键名以映射表 raw_key 大小写为准。
public Dictionary MapRow(
IDictionary row, IReadOnlyList maps)
{
var byContract = maps.ToDictionary(m => m.ContractField, StringComparer.OrdinalIgnoreCase);
var output = new Dictionary(StringComparer.OrdinalIgnoreCase);
foreach (var kv in row)
{
if (byContract.TryGetValue(kv.Key, out var map) && !string.IsNullOrWhiteSpace(map.RawKey))
output[map.RawKey] = kv.Value;
else
output[kv.Key] = kv.Value;
}
return output;
}
public List MissingRequired(IDictionary mapped, IReadOnlyList maps)
{
var missing = new List();
foreach (var map in maps.Where(m => m.IsRequired == 1))
{
var key = string.IsNullOrWhiteSpace(map.RawKey) ? map.ContractField : map.RawKey;
if (!TryGetNonEmpty(mapped, key))
missing.Add(map.ContractField);
}
return missing;
}
public MdpInboundSchemaData BuildSchema(
MdpEntity entity, IReadOnlyList maps)
{
var fields = new List();
var seen = new HashSet(StringComparer.OrdinalIgnoreCase);
void Add(MdpInboundSchemaField f)
{
if (seen.Add(f.Name))
fields.Add(f);
}
if (maps.Count > 0)
{
foreach (var map in maps)
{
Add(new MdpInboundSchemaField
{
Name = map.ContractField,
RawKey = string.IsNullOrWhiteSpace(map.RawKey) ? map.ContractField : map.RawKey,
Required = map.IsRequired == 1
});
}
}
else if (IdentityFields.TryGetValue(entity.EntityCode, out var identity))
{
foreach (var f in identity)
Add(f);
}
else if (!string.IsNullOrWhiteSpace(entity.BizKeyExpr))
{
foreach (var part in entity.BizKeyExpr.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
Add(Req(part));
}
foreach (var f in CommonContractFields)
Add(f);
return new MdpInboundSchemaData
{
EntityCode = entity.EntityCode,
BizKeyExpr = entity.BizKeyExpr,
Fields = fields
};
}
public static Dictionary ElementToDict(JsonElement el)
{
var dict = new Dictionary(StringComparer.OrdinalIgnoreCase);
if (el.ValueKind != JsonValueKind.Object)
return dict;
foreach (var prop in el.EnumerateObject())
{
dict[prop.Name] = prop.Value.ValueKind switch
{
JsonValueKind.Null => null,
JsonValueKind.String => prop.Value.GetString(),
JsonValueKind.Number => prop.Value.TryGetInt64(out var l) ? l
: prop.Value.TryGetDecimal(out var d) ? d
: prop.Value.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => prop.Value.GetRawText()
};
}
return dict;
}
private static bool TryGetNonEmpty(IDictionary row, string key)
{
foreach (var kv in row)
{
if (!string.Equals(kv.Key, key, StringComparison.OrdinalIgnoreCase))
continue;
if (kv.Value == null || kv.Value == DBNull.Value)
return false;
return !string.IsNullOrWhiteSpace(kv.Value.ToString());
}
return false;
}
private static MdpInboundSchemaField Req(string name) =>
new() { Name = name, RawKey = name, Required = true };
private static MdpInboundSchemaField Opt(string name) =>
new() { Name = name, RawKey = name, Required = false };
}