20261008-ide1.0发布 #1
@ -317,6 +317,25 @@ public partial class SalesProductService
|
||||
return priceSnapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态待发布快照:价区只解析哈希/已有包,不 Ensure 写库。
|
||||
/// </summary>
|
||||
private async Task<SalesProductPricePublishSnapshotDto> BuildPricePublishSnapshotForSyncAsync(int salesProductId)
|
||||
{
|
||||
var product = await GetProductOrThrowAsync(salesProductId);
|
||||
var priceSnapshot = new SalesProductPricePublishSnapshot
|
||||
{
|
||||
SalesProductId = salesProductId,
|
||||
Prices = (await GetPriceOutputsAsync(salesProductId)).Select(PublishSnapshotSlimmer.SlimPrice).ToList()
|
||||
};
|
||||
await PriceAreaSnapshotHelper.ApplyPriceAreaRefsForSyncAsync(
|
||||
priceSnapshot,
|
||||
product,
|
||||
KpiAreaRepository,
|
||||
KpiAreaPackService);
|
||||
return priceSnapshot;
|
||||
}
|
||||
|
||||
private string ResolvePublishUser() =>
|
||||
string.IsNullOrWhiteSpace(CurrentUser.UserName)
|
||||
? CurrentUser.GetId<int>().ToString()
|
||||
|
||||
@ -420,14 +420,13 @@ public partial class SalesProductService : ApplicationService
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
await GetProductOrThrowAsync(salesProductId);
|
||||
|
||||
// 两侧都走「序列化 → 反序列化规范化 → 再序列化」,避免刚发布仍误判待发布
|
||||
// (价格项排序、FactorValuesJson 的 Dictionary/JsonElement 形态差异等)
|
||||
// 待发布:同步态不 Ensure 分区包;对比时忽略 PackVersion/AreaName,只比价格项 + 分区 ContentHash
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(salesProductId)
|
||||
? await BuildPricePublishSnapshotForSyncAsync(salesProductId)
|
||||
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(pendingSnapshot)
|
||||
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(pendingSnapshot);
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(pendingSnapshot)
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(pendingSnapshot);
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
|
||||
var headers = await SalesProductRepository.GetAllPublishRecordHeadersAsync(salesProductId, normalized);
|
||||
@ -451,9 +450,9 @@ public partial class SalesProductService : ApplicationService
|
||||
var onlineRecord = await SalesProductRepository.GetPublishRecordByIdAsync(onlineHeader.Id);
|
||||
var onlineJson = onlineRecord?.Content ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeSalesProductPriceSnapshotJson(
|
||||
? OmsDocHelper.HashSalesProductPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializePriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.CanonicalizeSalesProductRulesSnapshotJson(
|
||||
: OmsDocHelper.HashSalesProductRulesSnapshotForSync(
|
||||
OmsDocHelper.DeserializeRulesSnapshot(onlineJson));
|
||||
var onlineHash = ComputeContentHash(onlineCanon);
|
||||
|
||||
|
||||
@ -262,6 +262,25 @@ public partial class ServiceChannelService
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 买价同步态:不 Ensure 新分区包。
|
||||
/// </summary>
|
||||
private async Task<ServiceChannelPricePublishSnapshotDto> BuildPricePublishSnapshotForSyncAsync(int serviceChannelId)
|
||||
{
|
||||
var snapshot = new ServiceChannelPricePublishSnapshotDto
|
||||
{
|
||||
ServiceChannelId = serviceChannelId,
|
||||
Prices = (await GetPriceOutputsAsync(serviceChannelId))
|
||||
.Select(PublishSnapshotSlimmer.SlimChannelPrice)
|
||||
.ToList()
|
||||
};
|
||||
await PriceAreaSnapshotHelper.ApplyChannelPriceAreaRefsForSyncAsync(
|
||||
snapshot,
|
||||
KpiAreaRepository,
|
||||
KpiAreaPackService);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组装渠道规则发布快照(不落库):钉偏远分区包。
|
||||
/// </summary>
|
||||
|
||||
@ -414,10 +414,10 @@ public partial class ServiceChannelService : ApplicationService
|
||||
await GetEntityOrThrowAsync(serviceChannelId);
|
||||
|
||||
object pendingSnapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(serviceChannelId)
|
||||
? await BuildPricePublishSnapshotForSyncAsync(serviceChannelId)
|
||||
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
||||
var pendingCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(pendingSnapshot)
|
||||
? OmsDocHelper.HashChannelPriceSnapshotForSync(pendingSnapshot)
|
||||
: OmsDocHelper.SerializeSnapshotJson(pendingSnapshot) ?? string.Empty;
|
||||
var pendingHash = ComputeContentHash(pendingCanon);
|
||||
|
||||
@ -439,7 +439,7 @@ public partial class ServiceChannelService : ApplicationService
|
||||
|
||||
var onlineJson = onlineRecord.Content ?? string.Empty;
|
||||
var onlineCanon = normalized == PriceOperatorType
|
||||
? OmsDocHelper.CanonicalizeChannelPriceSnapshotJson(
|
||||
? OmsDocHelper.HashChannelPriceSnapshotForSync(
|
||||
OmsDocHelper.DeserializeChannelPriceSnapshot(onlineJson))
|
||||
: OmsDocHelper.SerializeSnapshotJson(
|
||||
OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(onlineJson))
|
||||
|
||||
@ -41,6 +41,67 @@ public class KpiAreaPackService : IScopedDependency
|
||||
string? publishUser = null,
|
||||
string? description = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var prepared = await PreparePackContentAsync(areaId, cancellationToken);
|
||||
var existing = await _kpiAreaRepository.FindPublishByAreaIdAndContentHashAsync(
|
||||
areaId,
|
||||
prepared.ContentHash,
|
||||
cancellationToken);
|
||||
if (existing != null)
|
||||
{
|
||||
return ToRef(existing, prepared.AreaName);
|
||||
}
|
||||
|
||||
var version = DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
var record = new KpiAreaPublishRecord
|
||||
{
|
||||
AreaId = areaId,
|
||||
AreaType = string.IsNullOrWhiteSpace(areaType) ? prepared.AreaType : areaType.Trim(),
|
||||
Version = version,
|
||||
Status = EsSnapshotStatus.Active,
|
||||
ContentHash = prepared.ContentHash,
|
||||
Content = prepared.ContentJson,
|
||||
PublishUser = string.IsNullOrWhiteSpace(publishUser) ? "system" : publishUser.Trim(),
|
||||
PublishTime = DateTime.Now,
|
||||
Description = description
|
||||
};
|
||||
record = await _kpiAreaRepository.InsertPublishRecordAsync(record, cancellationToken);
|
||||
PackCache.Set(CacheKey(areaId, record.Version), prepared.Rules, new MemoryCacheEntryOptions
|
||||
{
|
||||
Size = 1,
|
||||
SlidingExpiration = TimeSpan.FromHours(6)
|
||||
});
|
||||
return ToRef(record, prepared.AreaName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态专用:只解析当前库明细哈希并回填已有包 Version;不落新包(避免 GET sync-status 写库)。
|
||||
/// </summary>
|
||||
public async Task<KpiAreaPackRefDto> ResolvePackRefForSyncAsync(
|
||||
int areaId,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var prepared = await PreparePackContentAsync(areaId, cancellationToken);
|
||||
var existing = await _kpiAreaRepository.FindPublishByAreaIdAndContentHashAsync(
|
||||
areaId,
|
||||
prepared.ContentHash,
|
||||
cancellationToken);
|
||||
if (existing != null)
|
||||
{
|
||||
return ToRef(existing, prepared.AreaName);
|
||||
}
|
||||
|
||||
return new KpiAreaPackRefDto
|
||||
{
|
||||
AreaId = areaId,
|
||||
AreaName = prepared.AreaName,
|
||||
PackVersion = string.Empty,
|
||||
ContentHash = prepared.ContentHash
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<(string AreaType, string? AreaName, string ContentJson, string ContentHash, List<KpiAreaSnapshotRuleDto> Rules)>
|
||||
PreparePackContentAsync(int areaId, CancellationToken cancellationToken)
|
||||
{
|
||||
if (areaId <= 0)
|
||||
{
|
||||
@ -57,36 +118,7 @@ public class KpiAreaPackService : IScopedDependency
|
||||
|
||||
var contentJson = OmsDocHelper.SerializeSnapshotJson(rules) ?? "[]";
|
||||
var contentHash = ComputeSha256Hex(contentJson);
|
||||
|
||||
var existing = await _kpiAreaRepository.FindPublishByAreaIdAndContentHashAsync(
|
||||
areaId,
|
||||
contentHash,
|
||||
cancellationToken);
|
||||
if (existing != null)
|
||||
{
|
||||
return ToRef(existing, area.Name);
|
||||
}
|
||||
|
||||
var version = DateTime.Now.ToString("yyyyMMddHHmmssfff");
|
||||
var record = new KpiAreaPublishRecord
|
||||
{
|
||||
AreaId = areaId,
|
||||
AreaType = string.IsNullOrWhiteSpace(areaType) ? area.AreaType : areaType.Trim(),
|
||||
Version = version,
|
||||
Status = EsSnapshotStatus.Active,
|
||||
ContentHash = contentHash,
|
||||
Content = contentJson,
|
||||
PublishUser = string.IsNullOrWhiteSpace(publishUser) ? "system" : publishUser.Trim(),
|
||||
PublishTime = DateTime.Now,
|
||||
Description = description
|
||||
};
|
||||
record = await _kpiAreaRepository.InsertPublishRecordAsync(record, cancellationToken);
|
||||
PackCache.Set(CacheKey(areaId, record.Version), rules, new MemoryCacheEntryOptions
|
||||
{
|
||||
Size = 1,
|
||||
SlidingExpiration = TimeSpan.FromHours(6)
|
||||
});
|
||||
return ToRef(record, area.Name);
|
||||
return (area.AreaType, area.Name, contentJson, contentHash, rules);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -177,6 +177,30 @@ public static class OmsDocHelper
|
||||
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>
|
||||
|
||||
@ -46,6 +46,46 @@ public static class PriceAreaSnapshotHelper
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
AssignPriceAreaRefs(snapshot, refs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态:解析价区 ContentHash / 已有包 Version,不 Ensure 新包。
|
||||
/// </summary>
|
||||
public static async Task ApplyPriceAreaRefsForSyncAsync(
|
||||
SalesProductPricePublishSnapshotDto snapshot,
|
||||
SalesProduct product,
|
||||
IKpiAreaRepository kpiAreaRepository,
|
||||
KpiAreaPackService packService,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var areaIds = await CollectResolvedPriceAreaIdsAsync(
|
||||
product,
|
||||
snapshot,
|
||||
kpiAreaRepository,
|
||||
cancellationToken);
|
||||
snapshot.PriceAreaInfos = [];
|
||||
if (areaIds.Count == 0)
|
||||
{
|
||||
snapshot.PriceAreaId = null;
|
||||
snapshot.PriceAreaName = null;
|
||||
snapshot.PriceAreaRefs = [];
|
||||
return;
|
||||
}
|
||||
|
||||
var refs = new List<KpiAreaPackRefDto>();
|
||||
foreach (var areaId in areaIds)
|
||||
{
|
||||
refs.Add(await packService.ResolvePackRefForSyncAsync(areaId, cancellationToken));
|
||||
}
|
||||
|
||||
AssignPriceAreaRefs(snapshot, refs);
|
||||
}
|
||||
|
||||
private static void AssignPriceAreaRefs(
|
||||
SalesProductPricePublishSnapshotDto snapshot,
|
||||
List<KpiAreaPackRefDto> refs)
|
||||
{
|
||||
snapshot.PriceAreaRefs = refs;
|
||||
if (refs.Count == 1)
|
||||
{
|
||||
@ -161,6 +201,44 @@ public static class PriceAreaSnapshotHelper
|
||||
cancellationToken));
|
||||
}
|
||||
|
||||
AssignChannelPriceAreaRefs(snapshot, refs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渠道买价同步态:不 Ensure 新包。
|
||||
/// </summary>
|
||||
public static async Task ApplyChannelPriceAreaRefsForSyncAsync(
|
||||
ServiceChannelPricePublishSnapshotDto snapshot,
|
||||
IKpiAreaRepository kpiAreaRepository,
|
||||
KpiAreaPackService packService,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var areaIds = await CollectResolvedChannelPriceAreaIdsAsync(
|
||||
snapshot,
|
||||
kpiAreaRepository,
|
||||
cancellationToken);
|
||||
snapshot.PriceAreaInfos = [];
|
||||
if (areaIds.Count == 0)
|
||||
{
|
||||
snapshot.PriceAreaId = null;
|
||||
snapshot.PriceAreaName = null;
|
||||
snapshot.PriceAreaRefs = [];
|
||||
return;
|
||||
}
|
||||
|
||||
var refs = new List<KpiAreaPackRefDto>();
|
||||
foreach (var areaId in areaIds)
|
||||
{
|
||||
refs.Add(await packService.ResolvePackRefForSyncAsync(areaId, cancellationToken));
|
||||
}
|
||||
|
||||
AssignChannelPriceAreaRefs(snapshot, refs);
|
||||
}
|
||||
|
||||
private static void AssignChannelPriceAreaRefs(
|
||||
ServiceChannelPricePublishSnapshotDto snapshot,
|
||||
List<KpiAreaPackRefDto> refs)
|
||||
{
|
||||
snapshot.PriceAreaRefs = refs;
|
||||
if (refs.Count == 1)
|
||||
{
|
||||
|
||||
@ -44,6 +44,61 @@ public static class PublishSnapshotNormalizer
|
||||
snapshot.Prices = SortChannelPriceItems(snapshot.Prices);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步态对比:分区包只比 AreaId+ContentHash(PackVersion 是钉包戳,AreaName 是展示名)。
|
||||
/// </summary>
|
||||
public static void StripVolatilePackPinsForSyncCompare(SalesProductPricePublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot.PriceAreaName = null;
|
||||
foreach (var packRef in snapshot.PriceAreaRefs ?? [])
|
||||
{
|
||||
StripPackPin(packRef);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StripVolatilePackPinsForSyncCompare(ServiceChannelPricePublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot.PriceAreaName = null;
|
||||
foreach (var packRef in snapshot.PriceAreaRefs ?? [])
|
||||
{
|
||||
StripPackPin(packRef);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StripVolatilePackPinsForSyncCompare(SalesProductRulesPublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot.BlacklistAreaName = null;
|
||||
snapshot.RemoteAreaName = null;
|
||||
StripPackPin(snapshot.BlacklistAreaRef);
|
||||
StripPackPin(snapshot.RemoteAreaRef);
|
||||
}
|
||||
|
||||
private static void StripPackPin(KpiAreaPackRefDto? packRef)
|
||||
{
|
||||
if (packRef is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
packRef.PackVersion = string.Empty;
|
||||
packRef.AreaName = null;
|
||||
}
|
||||
|
||||
public static void NormalizeKpiAreas(List<KpiAreaSnapshotRuleDto>? areas)
|
||||
{
|
||||
if (areas is null)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user