admin/src/Oms.Application/Shared/OmsDocHelper.cs
zzh 0449a4a3f5
All checks were successful
Build and Deploy / deploy (push) Successful in 57s
fix: 发布同步态按 ContentHash 语义对比,且不再 Ensure 写包
sync-status 用 ResolvePackRef;哈希忽略 PackVersion/AreaName。修复卖价页发布后因价格行签名跳过刷新导致徽章卡在待发布。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 19:21:17 +08:00

282 lines
9.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.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Oms.Application.Dtos.Orders;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.SystemAggregate.Consts;
namespace Oms.Application.Shared;
/// <summary>
/// OMS 通用文档与展示辅助方法。
/// </summary>
public static class OmsDocHelper
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true
};
private static readonly JsonSerializerOptions SnapshotJsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
/// <summary>
/// 将页码规范为至少 1。
/// </summary>
public static int NormalizePage(int page) => page <= 0 ? 1 : page;
/// <summary>
/// 将每页条数规范为 1~200。
/// </summary>
public static int NormalizePageSize(int pageSize) => pageSize <= 0 ? 20 : Math.Min(pageSize, 200);
/// <summary>
/// 将默认 DateTime 转为 null。
/// </summary>
public static DateTime? NullIfDefault(DateTime value) => value == default ? null : value;
/// <summary>
/// 将库内 JSON 字符串转为 API 可输出的 CLR 对象(避免 JsonElement 被 Newtonsoft 序列化成 valueKind)。
/// </summary>
public static object? DeserializeJsonObject(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return null;
}
using var document = JsonDocument.Parse(json);
return ConvertJsonElement(document.RootElement);
}
/// <summary>
/// 反序列化 JSON 字符串为指定类型。
/// </summary>
public static T? DeserializeJson<T>(string? json)
{
if (string.IsNullOrWhiteSpace(json))
{
return default;
}
return JsonSerializer.Deserialize<T>(json, JsonOptions);
}
/// <summary>
/// 将请求体中的动态 JSON(JsonElement / JToken / Dictionary 等)规范为可落库的 JSON 字符串。
/// </summary>
public static string? SerializeJson<T>(T? value)
{
if (value is null)
{
return null;
}
if (value is JsonElement element)
{
return element.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined
? null
: element.GetRawText();
}
if (value is JsonNode node)
{
return node.ToJsonString();
}
if (value is string text)
{
return string.IsNullOrWhiteSpace(text) ? null : text;
}
var typeName = value.GetType().FullName;
if (typeName is not null && typeName.StartsWith("Newtonsoft.Json.Linq.", StringComparison.Ordinal))
{
return value.ToString();
}
return JsonSerializer.Serialize(value, JsonOptions);
}
/// <summary>
/// 序列化发布快照(忽略 null,避免写出旧兼容字段)。
/// </summary>
public static string? SerializeSnapshotJson<T>(T? value)
{
if (value is null)
{
return null;
}
return JsonSerializer.Serialize(value, SnapshotJsonOptions);
}
/// <summary>
/// 将快照对象转为 API 输出结构(与落库 JSON 同构,避免预览/列表/落库序列化不一致)。
/// </summary>
public static object? ToSnapshotApiContent<T>(T? value)
{
if (value is null)
{
return null;
}
var json = SerializeSnapshotJson(value);
return string.IsNullOrWhiteSpace(json) ? null : DeserializeJsonObject(json);
}
public static SalesProductRulesPublishSnapshotDto? DeserializeRulesSnapshot(string? json)
{
var snapshot = DeserializeJson<SalesProductRulesPublishSnapshotDto>(json);
PublishSnapshotNormalizer.NormalizeRulesSnapshot(snapshot);
return snapshot;
}
public static SalesProductPricePublishSnapshotDto? DeserializePriceSnapshot(string? json)
{
var snapshot = DeserializeJson<SalesProductPricePublishSnapshotDto>(json);
PublishSnapshotNormalizer.NormalizePriceSnapshot(snapshot);
return snapshot;
}
public static ServiceChannelPricePublishSnapshotDto? DeserializeChannelPriceSnapshot(string? json)
{
var snapshot = DeserializeJson<ServiceChannelPricePublishSnapshotDto>(json);
PublishSnapshotNormalizer.NormalizeChannelPriceSnapshot(snapshot);
return snapshot;
}
/// <summary>
/// 同步态对比用:先序列化再反序列化规范化,消除 Dictionary/JsonElement、字段顺序等噪声。
/// </summary>
public static string CanonicalizeSalesProductPriceSnapshotJson(object? snapshot)
{
var raw = SerializeSnapshotJson(snapshot) ?? string.Empty;
return SerializeSnapshotJson(DeserializePriceSnapshot(raw)) ?? string.Empty;
}
/// <summary>
/// 同步态对比用:规则快照规范化 canonical JSON。
/// </summary>
public static string CanonicalizeSalesProductRulesSnapshotJson(object? snapshot)
{
var raw = SerializeSnapshotJson(snapshot) ?? string.Empty;
return SerializeSnapshotJson(DeserializeRulesSnapshot(raw)) ?? string.Empty;
}
/// <summary>
/// 同步态对比用:渠道买价快照规范化 canonical JSON。
/// </summary>
public static string CanonicalizeChannelPriceSnapshotJson(object? snapshot)
{
var raw = SerializeSnapshotJson(snapshot) ?? string.Empty;
return SerializeSnapshotJson(DeserializeChannelPriceSnapshot(raw)) ?? string.Empty;
}
/// <summary>
/// 发布同步态哈希:canonical + 去掉 PackVersion/AreaName 等钉包展示字段。
/// </summary>
public static string HashSalesProductPriceSnapshotForSync(object? snapshot)
{
var dto = DeserializePriceSnapshot(SerializeSnapshotJson(snapshot));
PublishSnapshotNormalizer.StripVolatilePackPinsForSyncCompare(dto);
return SerializeSnapshotJson(dto) ?? string.Empty;
}
public static string HashSalesProductRulesSnapshotForSync(object? snapshot)
{
var dto = DeserializeRulesSnapshot(SerializeSnapshotJson(snapshot));
PublishSnapshotNormalizer.StripVolatilePackPinsForSyncCompare(dto);
return SerializeSnapshotJson(dto) ?? string.Empty;
}
public static string HashChannelPriceSnapshotForSync(object? snapshot)
{
var dto = DeserializeChannelPriceSnapshot(SerializeSnapshotJson(snapshot));
PublishSnapshotNormalizer.StripVolatilePackPinsForSyncCompare(dto);
return SerializeSnapshotJson(dto) ?? string.Empty;
}
/// <summary>
/// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。
/// </summary>
public static object? ParseChannelPricePublishContentForApi(string? json)
{
var snapshot = DeserializeChannelPriceSnapshot(json);
return snapshot is null ? DeserializeJsonObject(json) : ToSnapshotApiContent(snapshot);
}
/// <summary>
/// 解析产品销售卖价发布 Content,供列表/对比 API 使用。
/// </summary>
public static object? ParseSalesProductPricePublishContentForApi(string? json)
{
var snapshot = DeserializePriceSnapshot(json);
return snapshot is null ? DeserializeJsonObject(json) : ToSnapshotApiContent(snapshot);
}
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
{
JsonValueKind.Object => element.EnumerateObject()
.ToDictionary(property => property.Name, property => ConvertJsonElement(property.Value)),
JsonValueKind.Array => element.EnumerateArray()
.Select(ConvertJsonElement)
.ToList(),
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.TryGetInt64(out var integer)
? integer
: element.GetDecimal(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null or JsonValueKind.Undefined => null,
_ => element.GetRawText()
};
/// <summary>
/// 反序列化 JSON 字符串数组。
/// </summary>
public static List<string> DeserializeStringList(string? json) => JsonStringArrayHelper.Deserialize(json);
/// <summary>
/// 将订单状态码转为中文名称。
/// </summary>
public static string? GetOrderStatusName(string statusCode)
{
return statusCode switch
{
OrderStatusCodes.Draft => "草稿",
OrderStatusCodes.Predeclared => "已预报",
OrderStatusCodes.PendingAllocation => "待排货",
OrderStatusCodes.PendingPayment => "待支付",
OrderStatusCodes.PendingShip => "待发货",
OrderStatusCodes.PendingOnline => "待上网",
OrderStatusCodes.Shipping => "运输中",
OrderStatusCodes.DeliverySuccess => "派送成功",
OrderStatusCodes.DeliveryFailed => "派送失败",
OrderStatusCodes.Problematic => "问题件",
OrderStatusCodes.Deleted => "已删除",
OrderStatusCodes.Claimed => "理赔件",
_ => statusCode
};
}
/// <summary>
/// 将仓库类型码转为中文名称。
/// </summary>
public static string GetWarehouseTypeName(string warehouseType)
{
return warehouseType switch
{
WarehouseTypeCodes.Main => "主仓",
WarehouseTypeCodes.Transit => "中转仓",
WarehouseTypeCodes.Return => "退货仓",
WarehouseTypeCodes.Other => "其他",
_ => warehouseType
};
}
}