20261008-ide1.0发布 #1

Merged
zzh merged 42 commits from test into master 2026-10-08 09:18:00 +08:00
5 changed files with 62 additions and 14 deletions
Showing only changes of commit 73f334edc8 - Show all commits

View File

@ -371,6 +371,11 @@ public partial class SalesProductService
snapshot = await BuildRulesPublishSnapshotAsync(salesProductId); snapshot = await BuildRulesPublishSnapshotAsync(salesProductId);
} }
// Content 存 canonical 形态,与 sync-status 对比口径一致
var content = operatorType == PriceOperatorType
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(snapshot)
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(snapshot);
// Content 中保存完整快照,回滚时直接从快照恢复,避免依赖当前库内状态。 // Content 中保存完整快照,回滚时直接从快照恢复,避免依赖当前库内状态。
await SalesProductRepository.InsertPublishRecordAsync(new SalesProductPublishRecord await SalesProductRepository.InsertPublishRecordAsync(new SalesProductPublishRecord
{ {
@ -382,7 +387,7 @@ public partial class SalesProductService
PublishTime = DateTime.Now, PublishTime = DateTime.Now,
Description = description, Description = description,
AiAnalysisReport = string.IsNullOrWhiteSpace(aiAnalysisReport) ? null : aiAnalysisReport.Trim(), AiAnalysisReport = string.IsNullOrWhiteSpace(aiAnalysisReport) ? null : aiAnalysisReport.Trim(),
Content = OmsDocHelper.SerializeSnapshotJson(snapshot) Content = content
}); });
} }

View File

@ -420,11 +420,15 @@ public partial class SalesProductService : ApplicationService
var normalized = NormalizeOperatorType(operatorType); var normalized = NormalizeOperatorType(operatorType);
await GetProductOrThrowAsync(salesProductId); await GetProductOrThrowAsync(salesProductId);
// 两侧都走「序列化 → 反序列化规范化 → 再序列化」,避免刚发布仍误判待发布
// (价格项排序、FactorValuesJson 的 Dictionary/JsonElement 形态差异等)
object pendingSnapshot = normalized == PriceOperatorType object pendingSnapshot = normalized == PriceOperatorType
? await BuildPricePublishSnapshotAsync(salesProductId) ? await BuildPricePublishSnapshotAsync(salesProductId)
: await BuildRulesPublishSnapshotAsync(salesProductId); : await BuildRulesPublishSnapshotAsync(salesProductId);
var pendingJson = OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty; var pendingCanon = normalized == PriceOperatorType
var pendingHash = ComputeContentHash(pendingJson); ? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(pendingSnapshot)
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(pendingSnapshot);
var pendingHash = ComputeContentHash(pendingCanon);
var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized); var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized);
var onlineHeader = headers var onlineHeader = headers
@ -446,11 +450,11 @@ public partial class SalesProductService : ApplicationService
var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id); var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id);
var onlineJson = onlineRecord?.Content ?? string.Empty; var onlineJson = onlineRecord?.Content ?? string.Empty;
// 线上 Content 与待发布统一经反序列化再序列化,避免字段顺序噪声 var onlineCanon = normalized == PriceOperatorType
object? onlineNormalized = normalized == PriceOperatorType ? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(
? OmsDocHelper.DeserializePriceSnapshot(onlineJson) OmsDocHelper.DeserializePriceSnapshot(onlineJson))
: OmsDocHelper.DeserializeRulesSnapshot(onlineJson); : OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(
var onlineCanon = OmsDocHelper.SerializeSnapshotJson(onlineNormalized) ?? string.Empty; OmsDocHelper.DeserializeRulesSnapshot(onlineJson));
var onlineHash = ComputeContentHash(onlineCanon); var onlineHash = ComputeContentHash(onlineCanon);
return new SalesProductPublishSyncStatusOutput return new SalesProductPublishSyncStatusOutput

View File

@ -416,8 +416,10 @@ public partial class ServiceChannelService : ApplicationService
object pendingSnapshot = normalized == PriceOperatorType object pendingSnapshot = normalized == PriceOperatorType
? await BuildPricePublishSnapshotAsync(serviceChannelId) ? await BuildPricePublishSnapshotAsync(serviceChannelId)
: await BuildRulesPublishSnapshotAsync(serviceChannelId); : await BuildRulesPublishSnapshotAsync(serviceChannelId);
var pendingJson = OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty; var pendingCanon = normalized == PriceOperatorType
var pendingHash = ComputeContentHash(pendingJson); ? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(pendingSnapshot)
: OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
var pendingHash = ComputeContentHash(pendingCanon);
var onlineRecord = await ServiceChannelRepository.GetEffectivePublishRecordAsync( var onlineRecord = await ServiceChannelRepository.GetEffectivePublishRecordAsync(
serviceChannelId, serviceChannelId,
@ -436,10 +438,12 @@ public partial class ServiceChannelService : ApplicationService
} }
var onlineJson = onlineRecord.Content ?? string.Empty; var onlineJson = onlineRecord.Content ?? string.Empty;
object? onlineNormalized = normalized == PriceOperatorType var onlineCanon = normalized == PriceOperatorType
? OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson) ? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(
: OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson); OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson))
var onlineCanon = OmsDocHelper.SerializeSnapshotJson(onlineNormalized) ?? string.Empty; : OmsDocHelper.SerializeSnapshotJson(
OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson))
?? string.Empty;
var onlineHash = ComputeContentHash(onlineCanon); var onlineHash = ComputeContentHash(onlineCanon);
return new ServiceChannelPublishSyncStatusOutput return new ServiceChannelPublishSyncStatusOutput

View File

@ -150,6 +150,33 @@ public static class OmsDocHelper
return 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> /// <summary>
/// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。 /// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。
/// </summary> /// </summary>

View File

@ -28,6 +28,7 @@ public static class PublishSnapshotNormalizer
} }
NormalizeKpiAreas(snapshot.PriceAreaInfos); NormalizeKpiAreas(snapshot.PriceAreaInfos);
snapshot.PriceAreaRefs = SortPackRefs(snapshot.PriceAreaRefs);
snapshot.Prices = SortSalesProductPriceItems(snapshot.Prices); snapshot.Prices = SortSalesProductPriceItems(snapshot.Prices);
} }
@ -39,6 +40,7 @@ public static class PublishSnapshotNormalizer
} }
NormalizeKpiAreas(snapshot.PriceAreaInfos); NormalizeKpiAreas(snapshot.PriceAreaInfos);
snapshot.PriceAreaRefs = SortPackRefs(snapshot.PriceAreaRefs);
snapshot.Prices = SortChannelPriceItems(snapshot.Prices); 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( private static List<SalesProductPriceSnapshotItemDto> SortSalesProductPriceItems(
List<SalesProductPriceSnapshotItemDto>? prices) => List<SalesProductPriceSnapshotItemDto>? prices) =>
prices? prices?