admin/src/Oms.Application/Shared/AllocationProviderSnapshotEnricher.cs
zzh fffecb5964 Fix downstream forecast: pass auth + cache itfx app secret
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 18:08:09 +08:00

196 lines
7.1 KiB
C#

using Fake.DependencyInjection;
using Oms.Application.Dtos.Channels;
using Oms.Application.Integrations.Itfx;
using Oms.Domain.ChannelsAggregate.Entities;
using Oms.Domain.ChannelsAggregate.Repositories;
namespace Oms.Application.Shared;
/// <summary>
/// 为排货 Provider 快照补充渠道与 itfx 下游对接元数据。
/// </summary>
public class AllocationProviderSnapshotEnricher(
IServiceChannelRepository serviceChannelRepository,
IServiceChannelGroupRepository serviceChannelGroupRepository,
IItfxMetadataClient itfxMetadataClient) : IScopedDependency
{
public async Task EnrichAsync(
IList<SalesProductAllocationProviderSnapshotDto> providers,
CancellationToken cancellationToken = default)
{
if (providers.Count == 0)
{
return;
}
var channelIds = providers
.Where(x => x.ServiceChannelId.HasValue && x.ServiceChannelId.Value > 0)
.Select(x => x.ServiceChannelId!.Value)
.Distinct()
.ToArray();
if (channelIds.Length == 0)
{
return;
}
var channels = await serviceChannelRepository.GetListAsync(x => channelIds.Contains(x.Id));
var channelMap = channels.ToDictionary(x => x.Id);
var groupIds = channels
.Where(x => x.GroupId.HasValue && x.GroupId.Value > 0)
.Select(x => x.GroupId!.Value)
.Distinct()
.ToArray();
var groups = groupIds.Length == 0
? []
: await serviceChannelGroupRepository.GetListAsync(x => groupIds.Contains(x.Id));
var groupMap = groups.ToDictionary(x => x.Id);
foreach (var provider in providers)
{
if (!provider.ServiceChannelId.HasValue
|| !channelMap.TryGetValue(provider.ServiceChannelId.Value, out var channel))
{
continue;
}
ApplyChannelFields(provider, channel);
if (!channel.GroupId.HasValue || !groupMap.TryGetValue(channel.GroupId.Value, out var group))
{
continue;
}
await ApplyGroupDownstreamFieldsAsync(provider, group, cancellationToken);
}
}
public async Task<ServiceChannelDownstreamSnapshotDto?> BuildDownstreamFromGroupAsync(
ServiceChannelGroup group,
CancellationToken cancellationToken = default)
{
var snapshot = new ServiceChannelDownstreamSnapshotDto
{
GroupId = group.Id,
GroupName = NullIfEmpty(group.Name),
AppId = group.AppId > 0 ? group.AppId : null,
ApiServiceProviderId = group.ApiServiceProviderId > 0 ? group.ApiServiceProviderId : null
};
await FillItfxMetadataAsync(snapshot, cancellationToken);
return snapshot.GroupId.HasValue
|| !string.IsNullOrWhiteSpace(snapshot.GroupName)
|| snapshot.AppId is > 0
|| !string.IsNullOrWhiteSpace(snapshot.AppKey)
|| !string.IsNullOrWhiteSpace(snapshot.AppName)
|| snapshot.ApiServiceProviderId is > 0
|| !string.IsNullOrWhiteSpace(snapshot.SupplierCode)
|| !string.IsNullOrWhiteSpace(snapshot.ApiServiceProviderName)
? snapshot
: null;
}
public static void ApplyChannelFields(SalesProductAllocationProviderSnapshotDto provider, ServiceChannel channel)
{
provider.ChannelCode = NullIfEmpty(channel.ChannelCode);
provider.ForecastCode = NullIfEmpty(channel.ForecastCode);
provider.GroupId = channel.GroupId;
if (string.IsNullOrWhiteSpace(provider.ServiceChannelName))
{
provider.ServiceChannelName = NullIfEmpty(channel.ChannelName);
}
}
public static void ApplyDownstreamFieldsToOrder(
Domain.OrdersAggregate.Entities.Order order,
SalesProductAllocationProviderSnapshotDto provider,
ServiceChannel? channelFallback = null)
{
order.DownstreamSupplierCode = NullIfEmpty(provider.SupplierCode);
order.DownstreamAppKey = NullIfEmpty(provider.AppKey);
order.DownstreamAppId = provider.AppId;
order.ChannelForecastCode = NullIfEmpty(provider.ForecastCode)
?? NullIfEmpty(channelFallback?.ForecastCode);
}
private async Task ApplyGroupDownstreamFieldsAsync(
SalesProductAllocationProviderSnapshotDto provider,
ServiceChannelGroup group,
CancellationToken cancellationToken)
{
provider.GroupId = group.Id;
provider.GroupName = NullIfEmpty(group.Name);
provider.AppId = group.AppId > 0 ? group.AppId : null;
provider.ApiServiceProviderId = group.ApiServiceProviderId > 0 ? group.ApiServiceProviderId : null;
if (provider.AppId is > 0)
{
var app = await itfxMetadataClient.GetAppAsync(provider.AppId.Value, cancellationToken);
if (!string.IsNullOrWhiteSpace(app?.AppKey))
{
provider.AppKey = app.AppKey;
}
provider.AppName = NullIfEmpty(app?.Name);
}
if (provider.ApiServiceProviderId is > 0)
{
var sp = await itfxMetadataClient.GetServiceProviderAsync(
provider.ApiServiceProviderId.Value,
cancellationToken);
if (!string.IsNullOrWhiteSpace(sp?.Code))
{
provider.SupplierCode = sp.Code;
}
provider.ApiServiceProviderName = NullIfEmpty(sp?.Name);
}
}
private async Task FillItfxMetadataAsync(
ServiceChannelDownstreamSnapshotDto snapshot,
CancellationToken cancellationToken)
{
if (snapshot.AppId is > 0)
{
var app = await itfxMetadataClient.GetAppAsync(snapshot.AppId.Value, cancellationToken);
if (!string.IsNullOrWhiteSpace(app?.AppKey))
{
snapshot.AppKey = app.AppKey;
}
if (!string.IsNullOrWhiteSpace(app?.AppSecret))
{
snapshot.AppSecret = app.AppSecret;
}
snapshot.AppName = NullIfEmpty(app?.Name);
}
if (snapshot.ApiServiceProviderId is > 0)
{
var sp = await itfxMetadataClient.GetServiceProviderAsync(
snapshot.ApiServiceProviderId.Value,
cancellationToken);
if (!string.IsNullOrWhiteSpace(sp?.Code))
{
snapshot.SupplierCode = sp.Code;
}
snapshot.ApiServiceProviderName = NullIfEmpty(sp?.Name);
}
}
private static string? NullIfEmpty(string? value) =>
string.IsNullOrWhiteSpace(value) ? null : value.Trim();
public static bool NeedsDownstreamEnrichment(SalesProductAllocationProviderSnapshotDto provider) =>
provider.ServiceChannelId is > 0
&& (string.IsNullOrWhiteSpace(provider.ChannelCode)
|| string.IsNullOrWhiteSpace(provider.ForecastCode)
|| string.IsNullOrWhiteSpace(provider.SupplierCode)
|| string.IsNullOrWhiteSpace(provider.AppKey));
}