All checks were successful
Build and Deploy / deploy (push) Successful in 57s
sync-status 用 ResolvePackRef;哈希忽略 PackVersion/AreaName。修复卖价页发布后因价格行签名跳过刷新导致徽章卡在待发布。 Co-authored-by: Cursor <cursoragent@cursor.com>
318 lines
10 KiB
C#
318 lines
10 KiB
C#
using Oms.Application.Dtos.Orders;
|
||
using Oms.Application.Services.Orders;
|
||
using Oms.Domain.ChannelsAggregate.Entities;
|
||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||
|
||
namespace Oms.Application.Shared;
|
||
|
||
/// <summary>
|
||
/// 卖价发布:解析价区表 Id 并钉分区包引用(不内嵌 Infos)。
|
||
/// </summary>
|
||
public static class PriceAreaSnapshotHelper
|
||
{
|
||
/// <summary>
|
||
/// 写入 PriceAreaRefs(Ensure 分区包);Infos 仅 LIVE 路径由 PackService.LoadLive 填充。
|
||
/// </summary>
|
||
public static async Task ApplyPriceAreaToSnapshotAsync(
|
||
SalesProductPricePublishSnapshotDto snapshot,
|
||
SalesProduct product,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
KpiAreaPackService packService,
|
||
string? publishUser = null,
|
||
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.EnsurePackAsync(
|
||
areaId,
|
||
KpiAreaPackService.AreaTypePrice,
|
||
publishUser,
|
||
description: "sales-product-price-publish",
|
||
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)
|
||
{
|
||
snapshot.PriceAreaId = refs[0].AreaId;
|
||
snapshot.PriceAreaName = refs[0].AreaName;
|
||
}
|
||
else
|
||
{
|
||
snapshot.PriceAreaId = null;
|
||
snapshot.PriceAreaName = string.Join(
|
||
"、",
|
||
refs.Select(x => x.AreaName).Where(x => !string.IsNullOrWhiteSpace(x)));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// LIVE/模拟:不钉包,直接从当前库装载 Infos。
|
||
/// </summary>
|
||
public static async Task ApplyLivePriceAreaInfosAsync(
|
||
SalesProductPricePublishSnapshotDto snapshot,
|
||
SalesProduct product,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
KpiAreaPackService packService,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var areaIds = await CollectResolvedPriceAreaIdsAsync(
|
||
product,
|
||
snapshot,
|
||
kpiAreaRepository,
|
||
cancellationToken);
|
||
await packService.LoadLiveInfosIntoPriceSnapshotAsync(snapshot, areaIds, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收集卖价明细(及产品 PriceAreaId)可解析到的全部价格分区表 Id,去重保序。
|
||
/// </summary>
|
||
internal static async Task<IReadOnlyList<int>> CollectResolvedPriceAreaIdsAsync(
|
||
SalesProduct product,
|
||
SalesProductPricePublishSnapshotDto snapshot,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var result = new List<int>();
|
||
var seen = new HashSet<int>();
|
||
|
||
void Add(int? areaId)
|
||
{
|
||
if (areaId is > 0 && seen.Add(areaId.Value))
|
||
{
|
||
result.Add(areaId.Value);
|
||
}
|
||
}
|
||
|
||
if (product.PriceAreaId is > 0)
|
||
{
|
||
Add(product.PriceAreaId);
|
||
}
|
||
|
||
var zoneKeys = ParcelBillingCalculator.CollectDistinctDetailZoneKeys(snapshot);
|
||
foreach (var zoneKey in zoneKeys)
|
||
{
|
||
Add(await TryResolveZoneKeyToAreaIdAsync(zoneKey, kpiAreaRepository, cancellationToken));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
internal static async Task<int?> ResolvePriceAreaIdAsync(
|
||
SalesProduct product,
|
||
SalesProductPricePublishSnapshotDto snapshot,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var ids = await CollectResolvedPriceAreaIdsAsync(
|
||
product,
|
||
snapshot,
|
||
kpiAreaRepository,
|
||
cancellationToken);
|
||
return ids.Count > 0 ? ids[0] : null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渠道买价发布:从明细推断价区表并钉包(与产品卖价分区独立)。
|
||
/// </summary>
|
||
public static async Task ApplyChannelPriceAreaToSnapshotAsync(
|
||
ServiceChannelPricePublishSnapshotDto snapshot,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
KpiAreaPackService packService,
|
||
string? publishUser = null,
|
||
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.EnsurePackAsync(
|
||
areaId,
|
||
KpiAreaPackService.AreaTypePrice,
|
||
publishUser,
|
||
description: "service-channel-price-publish",
|
||
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)
|
||
{
|
||
snapshot.PriceAreaId = refs[0].AreaId;
|
||
snapshot.PriceAreaName = refs[0].AreaName;
|
||
}
|
||
else
|
||
{
|
||
snapshot.PriceAreaId = null;
|
||
snapshot.PriceAreaName = string.Join(
|
||
"、",
|
||
refs.Select(x => x.AreaName).Where(x => !string.IsNullOrWhiteSpace(x)));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 渠道买价 LIVE:不钉包,从当前库装 Infos。
|
||
/// </summary>
|
||
public static async Task ApplyLiveChannelPriceAreaInfosAsync(
|
||
ServiceChannelPricePublishSnapshotDto snapshot,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
KpiAreaPackService packService,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var areaIds = await CollectResolvedChannelPriceAreaIdsAsync(
|
||
snapshot,
|
||
kpiAreaRepository,
|
||
cancellationToken);
|
||
await packService.LoadLiveInfosIntoChannelPriceSnapshotAsync(snapshot, areaIds, cancellationToken);
|
||
}
|
||
|
||
internal static async Task<IReadOnlyList<int>> CollectResolvedChannelPriceAreaIdsAsync(
|
||
ServiceChannelPricePublishSnapshotDto snapshot,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
CancellationToken cancellationToken = default)
|
||
{
|
||
var result = new List<int>();
|
||
var seen = new HashSet<int>();
|
||
|
||
void Add(int? areaId)
|
||
{
|
||
if (areaId is > 0 && seen.Add(areaId.Value))
|
||
{
|
||
result.Add(areaId.Value);
|
||
}
|
||
}
|
||
|
||
var zoneKeys = ParcelBillingCalculator.CollectDistinctDetailZoneKeys(snapshot);
|
||
foreach (var zoneKey in zoneKeys)
|
||
{
|
||
Add(await TryResolveZoneKeyToAreaIdAsync(zoneKey, kpiAreaRepository, cancellationToken));
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private static async Task<int?> TryResolveZoneKeyToAreaIdAsync(
|
||
string zoneKey,
|
||
IKpiAreaRepository kpiAreaRepository,
|
||
CancellationToken cancellationToken)
|
||
{
|
||
if (!int.TryParse(zoneKey.Trim(), out var zoneId) || zoneId <= 0)
|
||
{
|
||
return null;
|
||
}
|
||
|
||
var area = await kpiAreaRepository.FirstOrDefaultAsync(x => x.Id == zoneId, cancellationToken);
|
||
if (area != null)
|
||
{
|
||
return zoneId;
|
||
}
|
||
|
||
var info = await kpiAreaRepository.GetInfoByIdAsync(zoneId, cancellationToken);
|
||
return info?.AreaId;
|
||
}
|
||
}
|