fix: 卖价同步态对比改用 canonical JSON,避免误报待发布
All checks were successful
Build and Deploy / deploy (push) Successful in 59s
All checks were successful
Build and Deploy / deploy (push) Successful in 59s
待发布与线上两侧统一规范化后再算哈希,消除排序与 FactorValuesJson 形态差异导致的假 diff。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
1704c9f1e2
commit
73f334edc8
@ -371,6 +371,11 @@ public partial class SalesProductService
|
||||
snapshot = await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||
}
|
||||
|
||||
// Content 存 canonical 形态,与 sync-status 对比口径一致
|
||||
var content = operatorType == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(snapshot)
|
||||
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(snapshot);
|
||||
|
||||
// Content 中保存完整快照,回滚时直接从快照恢复,避免依赖当前库内状态。
|
||||
await SalesProductRepository.InsertPublishRecordAsync(new SalesProductPublishRecord
|
||||
{
|
||||
@ -382,7 +387,7 @@ public partial class SalesProductService
|
||||
PublishTime = DateTime.Now,
|
||||
Description = description,
|
||||
AiAnalysisReport = string.IsNullOrWhiteSpace(aiAnalysisReport) ? null : aiAnalysisReport.Trim(),
|
||||
Content = OmsDocHelper.SerializeSnapshotJson(snapshot)
|
||||
Content = content
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -420,11 +420,15 @@ public partial class SalesProductService : ApplicationService
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
await GetProductOrThrowAsync(salesProductId);
|
||||
|
||||
// 两侧都走「序列化 → 反序列化规范化 → 再序列化」,避免刚发布仍误判待发布
|
||||
// (价格项排序、FactorValuesJson 的 Dictionary/JsonElement 形态差异等)
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(salesProductId)
|
||||
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||
var pendingJson = OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
|
||||
var pendingHash = ComputeContentHash(pendingJson);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(pendingSnapshot)
|
||||
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(pendingSnapshot);
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
|
||||
var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized);
|
||||
var onlineHeader = headers
|
||||
@ -446,11 +450,11 @@ public partial class SalesProductService : ApplicationService
|
||||
|
||||
var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id);
|
||||
var onlineJson = onlineRecord?.Content ?? string.Empty;
|
||||
// 线上 Content 与待发布统一经反序列化再序列化,避免字段顺序噪声
|
||||
object? onlineNormalized = normalized == PriceOperatorType
|
||||
? OmsDocHelper.DeserializePriceSnapshot(onlineJson)
|
||||
: OmsDocHelper.DeserializeRulesSnapshot(onlineJson);
|
||||
var onlineCanon = OmsDocHelper.SerializeSnapshotJson(onlineNormalized) ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(
|
||||
OmsDocHelper.DeserializePriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(
|
||||
OmsDocHelper.DeserializeRulesSnapshot(onlineJson));
|
||||
var onlineHash = ComputeContentHash(onlineCanon);
|
||||
|
||||
return new SalesProductPublishSyncStatusOutput
|
||||
|
||||
@ -416,8 +416,10 @@ public partial class ServiceChannelService : ApplicationService
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(serviceChannelId)
|
||||
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
||||
var pendingJson = OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
|
||||
var pendingHash = ComputeContentHash(pendingJson);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(pendingSnapshot)
|
||||
: OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
|
||||
var onlineRecord = await ServiceChannelRepository.GetEffectivePublishRecordAsync(
|
||||
serviceChannelId,
|
||||
@ -436,10 +438,12 @@ public partial class ServiceChannelService : ApplicationService
|
||||
}
|
||||
|
||||
var onlineJson = onlineRecord.Content ?? string.Empty;
|
||||
object? onlineNormalized = normalized == PriceOperatorType
|
||||
? OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson)
|
||||
: OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson);
|
||||
var onlineCanon = OmsDocHelper.SerializeSnapshotJson(onlineNormalized) ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(
|
||||
OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.SerializeSnapshotJson(
|
||||
OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson))
|
||||
?? string.Empty;
|
||||
var onlineHash = ComputeContentHash(onlineCanon);
|
||||
|
||||
return new ServiceChannelPublishSyncStatusOutput
|
||||
|
||||
@ -150,6 +150,33 @@ public static class OmsDocHelper
|
||||
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>
|
||||
/// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。
|
||||
/// </summary>
|
||||
|
||||
@ -28,6 +28,7 @@ public static class PublishSnapshotNormalizer
|
||||
}
|
||||
|
||||
NormalizeKpiAreas(snapshot.PriceAreaInfos);
|
||||
snapshot.PriceAreaRefs = SortPackRefs(snapshot.PriceAreaRefs);
|
||||
snapshot.Prices = SortSalesProductPriceItems(snapshot.Prices);
|
||||
}
|
||||
|
||||
@ -39,6 +40,7 @@ public static class PublishSnapshotNormalizer
|
||||
}
|
||||
|
||||
NormalizeKpiAreas(snapshot.PriceAreaInfos);
|
||||
snapshot.PriceAreaRefs = SortPackRefs(snapshot.PriceAreaRefs);
|
||||
snapshot.Prices = SortChannelPriceItems(snapshot.Prices);
|
||||
}
|
||||
|
||||
@ -92,6 +94,12 @@ public static class PublishSnapshotNormalizer
|
||||
}
|
||||
}
|
||||
|
||||
private static List<KpiAreaPackRefDto> SortPackRefs(List<KpiAreaPackRefDto>? refs) =>
|
||||
refs?
|
||||
.OrderBy(x => x.AreaId)
|
||||
.ThenBy(x => x.PackVersion, StringComparer.Ordinal)
|
||||
.ToList() ?? [];
|
||||
|
||||
private static List<SalesProductPriceSnapshotItemDto> SortSalesProductPriceItems(
|
||||
List<SalesProductPriceSnapshotItemDto>? prices) =>
|
||||
prices?
|
||||
|
||||
Loading…
Reference in New Issue
Block a user