fix: sync-status 忽略价表明细数字 JSON 形态差异
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
LIVE 侧 DeserializeJsonObject 常把客户组价收到 long,历史 Content 里常见 133.0/0.0000;JS 结构相等但 SHA256 不等导致误报待发布。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7d14c0e5d9
commit
984799fcde
@ -1,6 +1,8 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Oms.Application.Dtos.Channels;
|
||||
using Oms.Application.Dtos.Orders;
|
||||
using Oms.Domain.OrdersAggregate.Consts;
|
||||
using Oms.Domain.SystemAggregate.Consts;
|
||||
@ -155,8 +157,9 @@ public static class OmsDocHelper
|
||||
/// </summary>
|
||||
public static string CanonicalizeSalesProductPriceSnapshotJson(object? snapshot)
|
||||
{
|
||||
var raw = SerializeSnapshotJson(snapshot) ?? string.Empty;
|
||||
return SerializeSnapshotJson(DeserializePriceSnapshot(raw)) ?? string.Empty;
|
||||
var dto = DeserializePriceSnapshot(SerializeSnapshotJson(snapshot));
|
||||
StabilizePriceSnapshotDynamicJson(dto);
|
||||
return SerializeSnapshotJson(dto) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -173,17 +176,19 @@ public static class OmsDocHelper
|
||||
/// </summary>
|
||||
public static string CanonicalizeChannelPriceSnapshotJson(object? snapshot)
|
||||
{
|
||||
var raw = SerializeSnapshotJson(snapshot) ?? string.Empty;
|
||||
return SerializeSnapshotJson(DeserializeChannelPriceSnapshot(raw)) ?? string.Empty;
|
||||
var dto = DeserializeChannelPriceSnapshot(SerializeSnapshotJson(snapshot));
|
||||
StabilizeChannelPriceSnapshotDynamicJson(dto);
|
||||
return SerializeSnapshotJson(dto) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发布同步态哈希:canonical + 去掉 PackVersion/AreaName 等钉包展示字段。
|
||||
/// 发布同步态哈希:canonical + 去掉 PackVersion/AreaName,并稳定动态 JSON 数字形态(133 vs 133.0、0 vs 0.0000)。
|
||||
/// </summary>
|
||||
public static string HashSalesProductPriceSnapshotForSync(object? snapshot)
|
||||
{
|
||||
var dto = DeserializePriceSnapshot(SerializeSnapshotJson(snapshot));
|
||||
PublishSnapshotNormalizer.StripVolatilePackPinsForSyncCompare(dto);
|
||||
StabilizePriceSnapshotDynamicJson(dto);
|
||||
return SerializeSnapshotJson(dto) ?? string.Empty;
|
||||
}
|
||||
|
||||
@ -198,9 +203,99 @@ public static class OmsDocHelper
|
||||
{
|
||||
var dto = DeserializeChannelPriceSnapshot(SerializeSnapshotJson(snapshot));
|
||||
PublishSnapshotNormalizer.StripVolatilePackPinsForSyncCompare(dto);
|
||||
StabilizeChannelPriceSnapshotDynamicJson(dto);
|
||||
return SerializeSnapshotJson(dto) ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把 FactorValuesJson / CustomerGroupValues / CalculatedValue 收到同一数字口径,避免 sync-status 误报待发布。
|
||||
/// </summary>
|
||||
private static void StabilizePriceSnapshotDynamicJson(SalesProductPricePublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot?.Prices == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var price in snapshot.Prices)
|
||||
{
|
||||
StabilizePriceDetails(price.Details);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StabilizeChannelPriceSnapshotDynamicJson(ServiceChannelPricePublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot?.Prices == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var price in snapshot.Prices)
|
||||
{
|
||||
StabilizePriceDetails(price.Details);
|
||||
}
|
||||
}
|
||||
|
||||
private static void StabilizePriceDetails(List<ChannelPriceDetailOutput>? details)
|
||||
{
|
||||
if (details == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var detail in details)
|
||||
{
|
||||
detail.FactorValuesJson = StabilizeDynamicJsonValue(detail.FactorValuesJson);
|
||||
detail.CustomerGroupValues = StabilizeDynamicJsonValue(detail.CustomerGroupValues);
|
||||
if (detail.CalculatedValue is { } calculated)
|
||||
{
|
||||
detail.CalculatedValue = StabilizeDecimal(calculated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// JsonElement / Dictionary 等动态 JSON 统一走 ConvertJsonElement,整数形态的小数收成 long。
|
||||
/// </summary>
|
||||
private static object? StabilizeDynamicJsonValue(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string? json = value switch
|
||||
{
|
||||
JsonElement element => element.ValueKind is JsonValueKind.Null or JsonValueKind.Undefined
|
||||
? null
|
||||
: element.GetRawText(),
|
||||
JsonNode node => node.ToJsonString(),
|
||||
string text => string.IsNullOrWhiteSpace(text) ? null : text,
|
||||
_ => SerializeJson(value)
|
||||
};
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
using var document = JsonDocument.Parse(json);
|
||||
return ConvertJsonElement(document.RootElement);
|
||||
}
|
||||
|
||||
private static decimal StabilizeDecimal(decimal value)
|
||||
{
|
||||
// 去掉尾随 0:0.0000 → 0,133.0 → 133(再由序列化写成整型 JSON)
|
||||
var normalized = decimal.Parse(value.ToString("G29"), CultureInfo.InvariantCulture);
|
||||
if (normalized == decimal.Truncate(normalized)
|
||||
&& normalized >= long.MinValue
|
||||
&& normalized <= long.MaxValue)
|
||||
{
|
||||
return normalized;
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。
|
||||
/// </summary>
|
||||
@ -227,15 +322,34 @@ public static class OmsDocHelper
|
||||
.Select(ConvertJsonElement)
|
||||
.ToList(),
|
||||
JsonValueKind.String => element.GetString(),
|
||||
JsonValueKind.Number => element.TryGetInt64(out var integer)
|
||||
? integer
|
||||
: element.GetDecimal(),
|
||||
JsonValueKind.Number => ConvertJsonNumber(element),
|
||||
JsonValueKind.True => true,
|
||||
JsonValueKind.False => false,
|
||||
JsonValueKind.Null or JsonValueKind.Undefined => null,
|
||||
_ => element.GetRawText()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 数字稳定化:能无损落成整型的一律 long,避免 133 / 133.0 导致发布 Content 与 LIVE 哈希不一致。
|
||||
/// </summary>
|
||||
private static object ConvertJsonNumber(JsonElement element)
|
||||
{
|
||||
if (element.TryGetInt64(out var integer))
|
||||
{
|
||||
return integer;
|
||||
}
|
||||
|
||||
var decimalValue = StabilizeDecimal(element.GetDecimal());
|
||||
if (decimalValue == decimal.Truncate(decimalValue)
|
||||
&& decimalValue >= long.MinValue
|
||||
&& decimalValue <= long.MaxValue)
|
||||
{
|
||||
return (long)decimalValue;
|
||||
}
|
||||
|
||||
return decimalValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反序列化 JSON 字符串数组。
|
||||
/// </summary>
|
||||
|
||||
@ -107,4 +107,107 @@ public class OmsDocHelperTests
|
||||
|
||||
typeName.Should().Be("中转仓");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HashSalesProductPriceSnapshotForSync_ShouldIgnoreNumberFormatNoise()
|
||||
{
|
||||
// LIVE:DeserializeJsonObject 常把价收到 long;历史 Content 里常见 133.0 / 0.0000
|
||||
var live = """
|
||||
{
|
||||
"salesProductId": 8,
|
||||
"priceAreaId": 3,
|
||||
"priceAreaName": "美国分区",
|
||||
"priceAreaRefs": [
|
||||
{
|
||||
"areaId": 3,
|
||||
"areaName": "美国分区",
|
||||
"packVersion": "20261007164410403",
|
||||
"contentHash": "e14ccf7436f001743ba9d45e0ec95f72285abd70b161fe91591766b909cefc80"
|
||||
}
|
||||
],
|
||||
"prices": [
|
||||
{
|
||||
"id": 13,
|
||||
"serialNumber": 1,
|
||||
"status": 2,
|
||||
"priceName": "运费",
|
||||
"costItem": "freight",
|
||||
"costUnit": "kg",
|
||||
"currency": "CNY",
|
||||
"chargingCondition": "无",
|
||||
"factors": [],
|
||||
"details": [
|
||||
{
|
||||
"id": 1511,
|
||||
"priceId": 13,
|
||||
"factorValuesJson": { "仓库": "2", "重量": "(0-0.5]" },
|
||||
"calculationFormula": "total",
|
||||
"calculatedValue": 0,
|
||||
"customerGroupValues": { "全部客户": 133 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
var online = """
|
||||
{
|
||||
"salesProductId": 8,
|
||||
"priceAreaId": 3,
|
||||
"priceAreaName": "美国分区",
|
||||
"priceAreaRefs": [
|
||||
{
|
||||
"areaId": 3,
|
||||
"areaName": "美国分区",
|
||||
"packVersion": "20261007164410403",
|
||||
"contentHash": "e14ccf7436f001743ba9d45e0ec95f72285abd70b161fe91591766b909cefc80"
|
||||
}
|
||||
],
|
||||
"prices": [
|
||||
{
|
||||
"id": 13,
|
||||
"serialNumber": 1,
|
||||
"status": 2,
|
||||
"priceName": "运费",
|
||||
"costItem": "freight",
|
||||
"costUnit": "kg",
|
||||
"currency": "CNY",
|
||||
"chargingCondition": "无",
|
||||
"factors": [],
|
||||
"details": [
|
||||
{
|
||||
"id": 1511,
|
||||
"priceId": 13,
|
||||
"factorValuesJson": { "仓库": "2", "重量": "(0-0.5]" },
|
||||
"calculationFormula": "total",
|
||||
"calculatedValue": 0.0000,
|
||||
"customerGroupValues": { "全部客户": 133.0 }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var liveHash = OmsDocHelper.HashSalesProductPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializePriceSnapshot(live));
|
||||
var onlineHash = OmsDocHelper.HashSalesProductPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializePriceSnapshot(online));
|
||||
|
||||
liveHash.Should().Be(onlineHash);
|
||||
liveHash.Should().Contain(":133}");
|
||||
liveHash.Should().Contain("\"calculatedValue\":0");
|
||||
liveHash.Should().NotContain("133.0");
|
||||
liveHash.Should().NotContain("0.0000");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializeJsonObject_ShouldCoerceIntegerValuedDecimalsToLong()
|
||||
{
|
||||
var result = OmsDocHelper.DeserializeJsonObject("""{"全部客户":133.0,"VIP":12.5}""");
|
||||
|
||||
var map = result.Should().BeOfType<Dictionary<string, object?>>().Subject;
|
||||
map["全部客户"].Should().Be(133L);
|
||||
map["VIP"].Should().Be(12.5m);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user