diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
index fff4f66..d7bc155 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
@@ -297,6 +297,29 @@ public partial class SalesProductService
return rulesSnapshot;
}
+ ///
+ /// 同步态规则快照:分区不 Ensure,其余与发布组装一致。
+ ///
+ private async Task BuildRulesPublishSnapshotForSyncAsync(int salesProductId)
+ {
+ var product = await GetProductOrThrowAsync(salesProductId);
+ var rulesSnapshot = new SalesProductRulesPublishSnapshot
+ {
+ Product = MapProductToRulesSaveInput(product),
+ Providers = (await GetAllocationCoreAsync(salesProductId)).Providers
+ .Select(PublishSnapshotSlimmer.SlimAllocationProvider)
+ .ToList()
+ };
+ await RulesSnapshotHelper.ApplyRulesToSnapshotForSyncAsync(
+ rulesSnapshot,
+ product,
+ WeightRuleRepository,
+ WeightRoundingRepository,
+ KpiAreaPackService);
+ await AllocationProviderSnapshotEnricher.EnrichAsync(rulesSnapshot.Providers);
+ return rulesSnapshot;
+ }
+
///
/// 组装价格发布快照(不落库);价区钉分区包 Refs。
///
diff --git a/src/Oms.Application/Services/Channels/SalesProductService.cs b/src/Oms.Application/Services/Channels/SalesProductService.cs
index c89d3b4..bba0e20 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.cs
@@ -410,7 +410,7 @@ public partial class SalesProductService : ApplicationService
}
///
- /// 发布同步态(轻量):对比待发布与最新生效快照,不回传正文。
+ /// 发布同步态:当前配置 vs 最新生效发布 Content。前端只展示 pending,不算差异。
///
[HttpGet("/channel/sales-product/publish/sync-status")]
public async Task GetPublishSyncStatusAsync(
@@ -420,48 +420,45 @@ public partial class SalesProductService : ApplicationService
var normalized = NormalizeOperatorType(operatorType);
await GetProductOrThrowAsync(salesProductId);
- // 待发布:同步态不 Ensure 分区包;对比时忽略 PackVersion/AreaName,只比价格项 + 分区 ContentHash
- object pendingSnapshot = normalized == PriceOperatorType
+ // 当前 = 与发布同源的快照组装(分区只解析哈希,不 Ensure 写库)
+ object currentSnapshot = normalized == PriceOperatorType
? await BuildPricePublishSnapshotForSyncAsync(salesProductId)
- : await BuildRulesPublishSnapshotAsync(salesProductId);
- var pendingCanon = normalized == PriceOperatorType
- ? OmsDocHelper.HashSalesProductPriceSnapshotForSync(pendingSnapshot)
- : OmsDocHelper.HashSalesProductRulesSnapshotForSync(pendingSnapshot);
- var pendingHash = ComputeContentHash(pendingCanon);
+ : await BuildRulesPublishSnapshotForSyncAsync(salesProductId);
+ var currentHash = ComputeContentHash(
+ normalized == PriceOperatorType
+ ? OmsDocHelper.HashSalesProductPriceSnapshotForSync(currentSnapshot)
+ : OmsDocHelper.HashSalesProductRulesSnapshotForSync(currentSnapshot));
- var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized);
- var onlineHeader = headers
- .Where(x => x.Status == Domain.Shared.Enums.EsSnapshotStatus.Active)
- .OrderByDescending(x => x.PublishTime)
- .FirstOrDefault();
-
- if (onlineHeader == null)
+ // 生效 = 最新 Active 发布记录 Content
+ var onlineRecord = await SalesProductRepository.GetEffectivePublishRecordAsync(
+ salesProductId,
+ normalized,
+ DateTime.Now);
+ if (onlineRecord == null || string.IsNullOrWhiteSpace(onlineRecord.Content))
{
return new SalesProductPublishSyncStatusOutput
{
Pending = true,
OnlineVersion = null,
OnlinePublishTime = null,
- PendingContentHash = pendingHash,
+ PendingContentHash = currentHash,
OnlineContentHash = null
};
}
- var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id);
- var onlineJson = onlineRecord?.Content ?? string.Empty;
- var onlineCanon = normalized == PriceOperatorType
- ? OmsDocHelper.HashSalesProductPriceSnapshotForSync(
- OmsDocHelper.DeserializePriceSnapshot(onlineJson))
- : OmsDocHelper.HashSalesProductRulesSnapshotForSync(
- OmsDocHelper.DeserializeRulesSnapshot(onlineJson));
- var onlineHash = ComputeContentHash(onlineCanon);
+ var onlineHash = ComputeContentHash(
+ normalized == PriceOperatorType
+ ? OmsDocHelper.HashSalesProductPriceSnapshotForSync(
+ OmsDocHelper.DeserializePriceSnapshot(onlineRecord.Content))
+ : OmsDocHelper.HashSalesProductRulesSnapshotForSync(
+ OmsDocHelper.DeserializeRulesSnapshot(onlineRecord.Content)));
return new SalesProductPublishSyncStatusOutput
{
- Pending = !string.Equals(pendingHash, onlineHash, StringComparison.Ordinal),
- OnlineVersion = onlineHeader.Version,
- OnlinePublishTime = onlineHeader.PublishTime,
- PendingContentHash = pendingHash,
+ Pending = !string.Equals(currentHash, onlineHash, StringComparison.Ordinal),
+ OnlineVersion = onlineRecord.Version,
+ OnlinePublishTime = onlineRecord.PublishTime,
+ PendingContentHash = currentHash,
OnlineContentHash = onlineHash
};
}
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
index 9f29b18..b986819 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs
@@ -325,6 +325,46 @@ public partial class ServiceChannelService
return snapshot;
}
+ ///
+ /// 渠道规则同步态:偏远分区不 Ensure。
+ ///
+ private async Task BuildRulesPublishSnapshotForSyncAsync(int serviceChannelId)
+ {
+ var entity = await GetEntityOrThrowAsync(serviceChannelId);
+ ServiceChannelDownstreamSnapshotDto? downstream = null;
+ if (entity.GroupId is > 0)
+ {
+ var group = await ServiceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == entity.GroupId.Value);
+ if (group != null)
+ {
+ downstream = await AllocationProviderSnapshotEnricher.BuildDownstreamFromGroupAsync(group);
+ }
+ }
+
+ var snapshot = new ServiceChannelRulesPublishSnapshotDto
+ {
+ Channel = MapChannelToSaveInput(entity),
+ Downstream = downstream,
+ RemoteAreaInfos = []
+ };
+
+ if (entity.RemoteAreaId is > 0)
+ {
+ var packRef = await KpiAreaPackService.ResolvePackRefForSyncAsync(entity.RemoteAreaId.Value);
+ snapshot.RemoteAreaId = entity.RemoteAreaId;
+ snapshot.RemoteAreaName = packRef.AreaName;
+ snapshot.RemoteAreaRef = packRef;
+ }
+ else
+ {
+ snapshot.RemoteAreaId = null;
+ snapshot.RemoteAreaName = null;
+ snapshot.RemoteAreaRef = null;
+ }
+
+ return snapshot;
+ }
+
private string ResolvePublishUser() =>
string.IsNullOrWhiteSpace(CurrentUser.UserName)
? CurrentUser.GetId().ToString()
diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
index 526b607..4e8336d 100644
--- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs
+++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs
@@ -403,7 +403,7 @@ public partial class ServiceChannelService : ApplicationService
}
///
- /// 发布同步态(轻量):对比待发布与最新生效快照,不回传正文。与产品卖价对称。
+ /// 发布同步态:当前配置 vs 最新生效发布 Content。前端只展示 pending。
///
[HttpGet("/channel/service-channel/publish/sync-status")]
public async Task GetPublishSyncStatusAsync(
@@ -413,49 +413,70 @@ public partial class ServiceChannelService : ApplicationService
var normalized = NormalizeOperatorType(operatorType);
await GetEntityOrThrowAsync(serviceChannelId);
- object pendingSnapshot = normalized == PriceOperatorType
- ? await BuildPricePublishSnapshotForSyncAsync(serviceChannelId)
- : await BuildRulesPublishSnapshotAsync(serviceChannelId);
- var pendingCanon = normalized == PriceOperatorType
- ? OmsDocHelper.HashChannelPriceSnapshotForSync(pendingSnapshot)
- : OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
- var pendingHash = ComputeContentHash(pendingCanon);
+ string currentCanon;
+ if (normalized == PriceOperatorType)
+ {
+ var current = await BuildPricePublishSnapshotForSyncAsync(serviceChannelId);
+ currentCanon = OmsDocHelper.HashChannelPriceSnapshotForSync(current);
+ }
+ else
+ {
+ var current = await BuildRulesPublishSnapshotForSyncAsync(serviceChannelId);
+ currentCanon = HashChannelRulesSnapshotForSync(current);
+ }
+
+ var currentHash = ComputeContentHash(currentCanon);
var onlineRecord = await ServiceChannelRepository.GetEffectivePublishRecordAsync(
serviceChannelId,
normalized,
DateTime.Now);
- if (onlineRecord == null)
+ if (onlineRecord == null || string.IsNullOrWhiteSpace(onlineRecord.Content))
{
return new ServiceChannelPublishSyncStatusOutput
{
Pending = true,
OnlineVersion = null,
OnlinePublishTime = null,
- PendingContentHash = pendingHash,
+ PendingContentHash = currentHash,
OnlineContentHash = null
};
}
- var onlineJson = onlineRecord.Content ?? string.Empty;
var onlineCanon = normalized == PriceOperatorType
? OmsDocHelper.HashChannelPriceSnapshotForSync(
- OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson))
- : OmsDocHelper.SerializeSnapshotJson(
- OmsDocHelper.DeserializeJson(onlineJson))
- ?? string.Empty;
+ OmsDocHelper.DeserializeChannelPriceSnapshot(onlineRecord.Content))
+ : HashChannelRulesSnapshotForSync(
+ OmsDocHelper.DeserializeJson(onlineRecord.Content));
var onlineHash = ComputeContentHash(onlineCanon);
return new ServiceChannelPublishSyncStatusOutput
{
- Pending = !string.Equals(pendingHash, onlineHash, StringComparison.Ordinal),
+ Pending = !string.Equals(currentHash, onlineHash, StringComparison.Ordinal),
OnlineVersion = onlineRecord.Version,
OnlinePublishTime = onlineRecord.PublishTime,
- PendingContentHash = pendingHash,
+ PendingContentHash = currentHash,
OnlineContentHash = onlineHash
};
}
+ private static string HashChannelRulesSnapshotForSync(ServiceChannelRulesPublishSnapshotDto? snapshot)
+ {
+ if (snapshot == null)
+ {
+ return string.Empty;
+ }
+
+ snapshot.RemoteAreaName = null;
+ if (snapshot.RemoteAreaRef != null)
+ {
+ snapshot.RemoteAreaRef.PackVersion = string.Empty;
+ snapshot.RemoteAreaRef.AreaName = null;
+ }
+
+ return OmsDocHelper.SerializeSnapshotJson(snapshot) ?? string.Empty;
+ }
+
private static string ComputeContentHash(string content)
{
var hash = global::System.Security.Cryptography.SHA256.HashData(
diff --git a/src/Oms.Application/Shared/RulesSnapshotHelper.cs b/src/Oms.Application/Shared/RulesSnapshotHelper.cs
index 5d64b2b..62c5154 100644
--- a/src/Oms.Application/Shared/RulesSnapshotHelper.cs
+++ b/src/Oms.Application/Shared/RulesSnapshotHelper.cs
@@ -28,6 +28,7 @@ public static class RulesSnapshotHelper
KpiAreaPackService.AreaTypeBlacklist,
packService,
publishUser,
+ ensurePack: true,
(s, areaId, areaName, packRef) =>
{
s.BlacklistAreaId = areaId;
@@ -43,6 +44,54 @@ public static class RulesSnapshotHelper
KpiAreaPackService.AreaTypeRemote,
packService,
publishUser,
+ ensurePack: true,
+ (s, areaId, areaName, packRef) =>
+ {
+ s.RemoteAreaId = areaId;
+ s.RemoteAreaName = areaName;
+ s.RemoteAreaRef = packRef;
+ s.RemoteAreaInfos = [];
+ },
+ cancellationToken);
+
+ await ApplyWeightRuleToSnapshotAsync(snapshot, product, weightRuleRepository, cancellationToken);
+ await ApplyWeightRoundingToSnapshotAsync(snapshot, product, weightRoundingRepository, cancellationToken);
+ }
+
+ ///
+ /// 同步态:黑名单/偏远只解析哈希/已有包,不 Ensure 写库。
+ ///
+ public static async Task ApplyRulesToSnapshotForSyncAsync(
+ SalesProductRulesPublishSnapshotDto snapshot,
+ SalesProduct product,
+ IWeightRuleRepository weightRuleRepository,
+ IWeightRoundingRepository weightRoundingRepository,
+ KpiAreaPackService packService,
+ CancellationToken cancellationToken = default)
+ {
+ await ApplyKpiAreaPackRefAsync(
+ snapshot,
+ product.BlacklistAreaId,
+ KpiAreaPackService.AreaTypeBlacklist,
+ packService,
+ publishUser: null,
+ ensurePack: false,
+ (s, areaId, areaName, packRef) =>
+ {
+ s.BlacklistAreaId = areaId;
+ s.BlacklistAreaName = areaName;
+ s.BlacklistAreaRef = packRef;
+ s.BlacklistAreaInfos = [];
+ },
+ cancellationToken);
+
+ await ApplyKpiAreaPackRefAsync(
+ snapshot,
+ product.RemoteAreaId,
+ KpiAreaPackService.AreaTypeRemote,
+ packService,
+ publishUser: null,
+ ensurePack: false,
(s, areaId, areaName, packRef) =>
{
s.RemoteAreaId = areaId;
@@ -107,6 +156,7 @@ public static class RulesSnapshotHelper
string areaType,
KpiAreaPackService packService,
string? publishUser,
+ bool ensurePack,
Action assign,
CancellationToken cancellationToken)
{
@@ -116,12 +166,14 @@ public static class RulesSnapshotHelper
return;
}
- var packRef = await packService.EnsurePackAsync(
- areaId.Value,
- areaType,
- publishUser,
- description: "sales-product-rules-publish",
- cancellationToken);
+ var packRef = ensurePack
+ ? await packService.EnsurePackAsync(
+ areaId.Value,
+ areaType,
+ publishUser,
+ description: "sales-product-rules-publish",
+ cancellationToken)
+ : await packService.ResolvePackRefForSyncAsync(areaId.Value, cancellationToken);
assign(snapshot, areaId, packRef.AreaName, packRef);
}