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();