From 15785cdf2bd26e98ac158f9626a6386b699fdf1b Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Thu, 23 Jul 2026 10:23:56 +0800 Subject: [PATCH] feat: enrich publish snapshots with downstream names and use API provider code for track ApiCode Persist group/app/API provider display names in sales product and service channel rules publish snapshots, and resolve trajectory ApiCode from the service channel API provider code instead of forecast code. --- ...lesProductAllocationProviderSnapshotDto.cs | 15 +++ .../ServiceChannelDownstreamSnapshotDto.cs | 47 ++++++++ .../Integrations/Itfx/IItfxMetadataClient.cs | 4 + .../Integrations/Itfx/ItfxMetadataClient.cs | 21 +++- .../SmallBagTrackUpsertRequest.cs | 2 +- .../SmallBagTrackUpsertRequestBuilder.cs | 69 ++++++++--- .../Channels/ServiceChannelService.Helpers.cs | 15 ++- .../Channels/ServiceChannelService.cs | 2 + .../AllocationProviderSnapshotEnricher.cs | 114 ++++++++++++++---- 9 files changed, 246 insertions(+), 43 deletions(-) create mode 100644 src/Oms.Application/Dtos/Channels/ServiceChannelDownstreamSnapshotDto.cs diff --git a/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs b/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs index fa6baa4..9793fa9 100644 --- a/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs +++ b/src/Oms.Application/Dtos/Channels/SalesProductAllocationProviderSnapshotDto.cs @@ -60,16 +60,31 @@ public class SalesProductAllocationProviderSnapshotDto /// public int? GroupId { get; set; } + /// + /// 服务商分组名称。 + /// + public string? GroupName { get; set; } + /// /// itfx API 服务商编码 supplierCode。 /// public string? SupplierCode { get; set; } + /// + /// itfx API 服务商名称。 + /// + public string? ApiServiceProviderName { get; set; } + /// /// itfx 应用 AppKey(AppSecret 不落快照,运行时按 AppId 读取)。 /// public string? AppKey { get; set; } + /// + /// itfx 应用名称。 + /// + public string? AppName { get; set; } + /// /// itfx 应用 Id。 /// diff --git a/src/Oms.Application/Dtos/Channels/ServiceChannelDownstreamSnapshotDto.cs b/src/Oms.Application/Dtos/Channels/ServiceChannelDownstreamSnapshotDto.cs new file mode 100644 index 0000000..abb2a5a --- /dev/null +++ b/src/Oms.Application/Dtos/Channels/ServiceChannelDownstreamSnapshotDto.cs @@ -0,0 +1,47 @@ +namespace Oms.Application.Dtos.Channels; + +/// +/// 服务渠道下游对接元数据(发布快照固化,不含 AppSecret)。 +/// +public class ServiceChannelDownstreamSnapshotDto +{ + /// + /// 服务商分组 Id。 + /// + public int? GroupId { get; set; } + + /// + /// 服务商分组名称。 + /// + public string? GroupName { get; set; } + + /// + /// itfx 应用 Id。 + /// + public int? AppId { get; set; } + + /// + /// itfx 应用 AppKey。 + /// + public string? AppKey { get; set; } + + /// + /// itfx 应用名称。 + /// + public string? AppName { get; set; } + + /// + /// itfx API 服务商 Id。 + /// + public int? ApiServiceProviderId { get; set; } + + /// + /// itfx API 服务商编码 supplierCode。 + /// + public string? SupplierCode { get; set; } + + /// + /// itfx API 服务商名称。 + /// + public string? ApiServiceProviderName { get; set; } +} diff --git a/src/Oms.Application/Integrations/Itfx/IItfxMetadataClient.cs b/src/Oms.Application/Integrations/Itfx/IItfxMetadataClient.cs index 427b03a..62a3348 100644 --- a/src/Oms.Application/Integrations/Itfx/IItfxMetadataClient.cs +++ b/src/Oms.Application/Integrations/Itfx/IItfxMetadataClient.cs @@ -5,11 +5,15 @@ public sealed class ItfxAppMetadata public string AppKey { get; set; } = string.Empty; public string AppSecret { get; set; } = string.Empty; + + public string? Name { get; set; } } public sealed class ItfxServiceProviderMetadata { public string Code { get; set; } = string.Empty; + + public string? Name { get; set; } } public interface IItfxMetadataClient diff --git a/src/Oms.Application/Integrations/Itfx/ItfxMetadataClient.cs b/src/Oms.Application/Integrations/Itfx/ItfxMetadataClient.cs index 36b0b83..5e545c5 100644 --- a/src/Oms.Application/Integrations/Itfx/ItfxMetadataClient.cs +++ b/src/Oms.Application/Integrations/Itfx/ItfxMetadataClient.cs @@ -30,10 +30,12 @@ internal sealed class ItfxMetadataClient( return null; } + var appName = ReadString(payload, "name", "Name", "appName", "AppName"); return new ItfxAppMetadata { AppKey = appKey.Trim(), - AppSecret = appSecret?.Trim() ?? string.Empty + AppSecret = appSecret?.Trim() ?? string.Empty, + Name = NullIfEmpty(appName) }; }, cancellationToken); @@ -45,9 +47,17 @@ internal sealed class ItfxMetadataClient( payload => { var code = ReadString(payload, "code", "Code", "serviceProviderCode", "ServiceProviderCode"); - return string.IsNullOrWhiteSpace(code) - ? null - : new ItfxServiceProviderMetadata { Code = code.Trim() }; + if (string.IsNullOrWhiteSpace(code)) + { + return null; + } + + var name = ReadString(payload, "name", "Name", "serviceProviderName", "ServiceProviderName"); + return new ItfxServiceProviderMetadata + { + Code = code.Trim(), + Name = NullIfEmpty(name) + }; }, cancellationToken); @@ -140,4 +150,7 @@ internal sealed class ItfxMetadataClient( return null; } + + private static string? NullIfEmpty(string? value) => + string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequest.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequest.cs index 2cbf4b5..949bd1a 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequest.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequest.cs @@ -18,7 +18,7 @@ public class SmallBagTrackUpsertRequest public string Product { get; set; } = string.Empty; /// - /// 轨迹拉取配置 code(对应轨迹 ServiceProviderCode;优先 ChannelForecastCode)。 + /// 轨迹拉取配置 code(对应轨迹 ServiceProviderCode;取服务渠道 API 服务商 code)。 /// public string ApiCode { get; set; } = string.Empty; diff --git a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequestBuilder.cs b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequestBuilder.cs index 7c20b4e..6ae8f4f 100644 --- a/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequestBuilder.cs +++ b/src/Oms.Application/Integrations/SmallBagTrack/SmallBagTrackUpsertRequestBuilder.cs @@ -1,5 +1,7 @@ using Fake.DependencyInjection; using Oms.Application.Dtos.Orders; +using Oms.Application.Integrations.Itfx; +using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.ChannelsAggregate.Repositories; using Oms.Domain.CustomersAggregate.Entities; using Oms.Domain.CustomersAggregate.Repositories; @@ -18,12 +20,14 @@ public interface ISmallBagTrackUpsertRequestBuilder } /// -/// 组装 UpsertSmallBag 请求:ApiCode 优先 ChannelForecastCode,TrackNo 缺省用 ServiceNo。 +/// 组装 UpsertSmallBag 请求:ApiCode 取服务渠道分组关联的 API 服务商 code,TrackNo 缺省用 ServiceNo。 /// internal sealed class SmallBagTrackUpsertRequestBuilder( IOrderRepository orderRepository, ICustomerRepository customerRepository, IServiceChannelRepository serviceChannelRepository, + IServiceChannelGroupRepository serviceChannelGroupRepository, + IItfxMetadataClient itfxMetadataClient, ISalesProductRepository salesProductRepository) : ISmallBagTrackUpsertRequestBuilder, IScopedDependency { public async Task BuildAsync( @@ -81,29 +85,66 @@ internal sealed class SmallBagTrackUpsertRequestBuilder( private async Task ResolveTrackApiCodeAsync(Order order, CancellationToken cancellationToken) { - var fromOrder = order.ChannelForecastCode?.Trim(); + var fromOrder = order.DownstreamSupplierCode?.Trim(); if (!string.IsNullOrWhiteSpace(fromOrder)) { return fromOrder; } - var channelCode = order.ChannelCode?.Trim(); - if (!string.IsNullOrWhiteSpace(channelCode)) + var fromChannel = await ResolveApiCodeFromChannelCodeAsync(order.ChannelCode, cancellationToken); + if (!string.IsNullOrWhiteSpace(fromChannel)) { - var channel = await serviceChannelRepository.GetByChannelCodeAsync(channelCode, cancellationToken); - var forecast = channel?.ForecastCode?.Trim(); - if (!string.IsNullOrWhiteSpace(forecast)) - { - return forecast; - } + return fromChannel; } var provisional = await ResolveProvisionalApiCodeFromProductAsync(order, cancellationToken); return provisional ?? string.Empty; } + private async Task ResolveApiCodeFromChannelCodeAsync( + string? channelCode, + CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(channelCode)) + { + return null; + } + + var channel = await serviceChannelRepository.GetByChannelCodeAsync(channelCode.Trim(), cancellationToken); + if (channel == null) + { + return null; + } + + return await ResolveApiCodeFromServiceChannelAsync(channel, cancellationToken); + } + + private async Task ResolveApiCodeFromServiceChannelAsync( + ServiceChannel channel, + CancellationToken cancellationToken) + { + if (!channel.GroupId.HasValue || channel.GroupId.Value <= 0) + { + return null; + } + + var group = await serviceChannelGroupRepository.FirstOrDefaultAsync( + x => x.Id == channel.GroupId.Value, + cancellationToken); + if (group == null || group.ApiServiceProviderId <= 0) + { + return null; + } + + var serviceProvider = await itfxMetadataClient.GetServiceProviderAsync( + group.ApiServiceProviderId, + cancellationToken); + var code = serviceProvider?.Code?.Trim(); + return string.IsNullOrWhiteSpace(code) ? null : code; + } + /// - /// 创单尚未排货时,用销售产品排货规则里首个渠道的 ForecastCode 作为轨迹 ApiCode 预占位。 + /// 创单尚未排货时,用销售产品排货规则里首个服务渠道的 API 服务商 code 作为轨迹 ApiCode 预占位。 /// private async Task ResolveProvisionalApiCodeFromProductAsync( Order order, @@ -135,10 +176,10 @@ internal sealed class SmallBagTrackUpsertRequestBuilder( var channels = await serviceChannelRepository.GetListAsync(x => channelIds.Contains(x.Id)); foreach (var channel in channels.OrderBy(x => x.Id)) { - var forecast = channel.ForecastCode?.Trim(); - if (!string.IsNullOrWhiteSpace(forecast)) + var apiCode = await ResolveApiCodeFromServiceChannelAsync(channel, cancellationToken); + if (!string.IsNullOrWhiteSpace(apiCode)) { - return forecast; + return apiCode; } } diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs index d5c4810..6de193d 100644 --- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs +++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs @@ -194,9 +194,20 @@ public partial class ServiceChannelService private async Task BuildRulesPublishSnapshotAsync(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); + } + } + return new ServiceChannelRulesPublishSnapshot { - Channel = MapChannelToSaveInput(entity) + Channel = MapChannelToSaveInput(entity), + Downstream = downstream }; } @@ -520,5 +531,7 @@ public partial class ServiceChannelService private sealed class ServiceChannelRulesPublishSnapshot { public ServiceChannelSaveInput Channel { get; set; } = new(); + + public ServiceChannelDownstreamSnapshotDto? Downstream { get; set; } } } diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs index 9609f8b..b7707aa 100644 --- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs +++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs @@ -20,6 +20,8 @@ public partial class ServiceChannelService : ApplicationService private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService(); private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService(); + private AllocationProviderSnapshotEnricher AllocationProviderSnapshotEnricher => + LazyServiceProvider.GetRequiredService(); private OmsRbacUserDisplayNameResolver UserDisplayNameResolver => LazyServiceProvider.GetRequiredService(); diff --git a/src/Oms.Application/Shared/AllocationProviderSnapshotEnricher.cs b/src/Oms.Application/Shared/AllocationProviderSnapshotEnricher.cs index 60117d2..2c26636 100644 --- a/src/Oms.Application/Shared/AllocationProviderSnapshotEnricher.cs +++ b/src/Oms.Application/Shared/AllocationProviderSnapshotEnricher.cs @@ -61,32 +61,36 @@ public class AllocationProviderSnapshotEnricher( continue; } - provider.GroupId = group.Id; - 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; - } - } - - if (provider.ApiServiceProviderId is > 0) - { - var sp = await itfxMetadataClient.GetServiceProviderAsync( - provider.ApiServiceProviderId.Value, - cancellationToken); - if (!string.IsNullOrWhiteSpace(sp?.Code)) - { - provider.SupplierCode = sp.Code; - } - } + await ApplyGroupDownstreamFieldsAsync(provider, group, cancellationToken); } } + public async Task 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); @@ -110,6 +114,70 @@ public class AllocationProviderSnapshotEnricher( ?? 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; + } + + 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();