admin/src/Oms.Application/Integrations/SmallBagTrack/PickupBigBagTrackSyncer.cs
zzh 6c8f7cb781
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
feat: 9610汇总下单必填电商平台写入预报,并增强官网产品目录筛选项
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-28 14:25:11 +08:00

270 lines
9.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
namespace Oms.Application.Integrations.SmallBagTrack;
/// <summary>
/// 揽收大包轨迹同步结果(UpsertBigBag → Link → Track A0201)。
/// </summary>
public class PickupBigBagTrackSyncResult
{
public string PickupNo { get; set; } = string.Empty;
public bool Success { get; set; }
public string? FailStep { get; set; }
public string? Message { get; set; }
public SmallBagTrackUpsertResult? UpsertBigBag { get; set; }
public SmallBagTrackUpsertResult? Link { get; set; }
public SmallBagTrackUpsertResult? TrackUpsert { get; set; }
}
/// <summary>
/// 揽收单预报下游成功后 / 手动:按序 UpsertBigBag → Link → Track/Upsert(A0201)。
/// </summary>
public interface IPickupBigBagTrackSyncer
{
Task SyncAfterDownstreamForwardAsync(PickupOrder pickup, CancellationToken cancellationToken = default);
/// <summary>
/// 同步执行三步并返回逐步请求/响应(手动「创建轨迹订单」联调)。
/// </summary>
Task<PickupBigBagTrackSyncResult> SyncDebugAsync(PickupOrder pickup, CancellationToken cancellationToken = default);
}
internal sealed class PickupBigBagTrackSyncer(
ISmallBagTrackOrderClient trackClient,
IPickupOrderLineRepository pickupOrderLineRepository,
ISalesProductRepository salesProductRepository,
IServiceChannelRepository serviceChannelRepository,
IHttpContextAccessor httpContextAccessor,
ILogger<PickupBigBagTrackSyncer> logger) : IPickupBigBagTrackSyncer
{
public async Task SyncAfterDownstreamForwardAsync(
PickupOrder pickup,
CancellationToken cancellationToken = default)
{
await SyncDebugAsync(pickup, cancellationToken);
}
public async Task<PickupBigBagTrackSyncResult> SyncDebugAsync(
PickupOrder pickup,
CancellationToken cancellationToken = default)
{
var result = new PickupBigBagTrackSyncResult
{
PickupNo = pickup?.PickupNo?.Trim() ?? string.Empty
};
if (pickup == null || string.IsNullOrWhiteSpace(pickup.PickupNo))
{
result.Success = false;
result.FailStep = "validate";
result.Message = "pickup or PickupNo empty";
logger.LogWarning("Skip big-bag track sync: pickup or PickupNo empty");
return result;
}
var bagNo = pickup.PickupNo.Trim();
result.PickupNo = bagNo;
var authorization = httpContextAccessor.HttpContext?.Request.Headers.Authorization.ToString();
if (string.IsNullOrWhiteSpace(authorization))
{
authorization = null;
}
try
{
var (channel, product) = await ResolveChannelProductAsync(pickup.CarrierService, cancellationToken);
var country = ResolveCountry(pickup);
// 1) UpsertBigBag:client 取 product(销售产品编码)
var upsertResult = await trackClient.UpsertBigBagAsync(
new SmallBagTrackUpsertBigBagRequest
{
Client = product ?? string.Empty,
BagNo = bagNo,
OtherNo = pickup.CarrierWaybillNo,
Channel = channel,
Product = product,
Country = country,
Authorization = authorization
},
cancellationToken);
result.UpsertBigBag = upsertResult;
if (upsertResult.Skipped)
{
result.Success = false;
result.FailStep = "UpsertBigBag";
result.Message = upsertResult.SkipReason ?? "skipped";
logger.LogDebug(
"UpsertBigBag skipped for pickup {PickupNo}: {Reason}",
bagNo,
upsertResult.SkipReason);
return result;
}
if (!upsertResult.Success)
{
result.Success = false;
result.FailStep = "UpsertBigBag";
result.Message = $"HTTP {upsertResult.StatusCode}";
logger.LogWarning(
"Stop big-bag track sync after UpsertBigBag failure for pickup {PickupNo}",
bagNo);
return result;
}
// 2) Link(无明细单号则跳过,继续写轨迹)
var lines = await pickupOrderLineRepository.GetByPickupOrderIdAsync(pickup.Id, cancellationToken);
var serviceNos = lines
.Select(x => x.ServiceNo)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Select(x => x!.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (serviceNos.Count > 0)
{
var linkResult = await trackClient.LinkOrdersAsync(
bagNo,
SmallBagTrackLifecycleEvents.BigBagOrderType,
serviceNos,
SmallBagTrackLifecycleEvents.OrderType,
authorization,
cancellationToken);
result.Link = linkResult;
if (linkResult.Skipped)
{
logger.LogDebug(
"Link skipped for pickup {PickupNo}: {Reason}",
bagNo,
linkResult.SkipReason);
}
else if (!linkResult.Success)
{
result.Success = false;
result.FailStep = "Link";
result.Message = $"HTTP {linkResult.StatusCode}";
logger.LogWarning(
"Stop big-bag track sync after Link failure for pickup {PickupNo}",
bagNo);
return result;
}
}
else
{
logger.LogDebug("Skip Link for pickup {PickupNo}: no bound ServiceNo", bagNo);
}
// 3) Track Upsert A0201
var trackResult = await trackClient.AppendLifecycleTrackResultAsync(
bagNo,
SmallBagTrackLifecycleEvents.PickupCreateTrackCode,
SmallBagTrackLifecycleEvents.BigBagOrderType,
authorization,
cancellationToken);
result.TrackUpsert = trackResult;
if (trackResult.Skipped)
{
result.Success = false;
result.FailStep = "TrackUpsert";
result.Message = trackResult.SkipReason ?? "skipped";
logger.LogDebug(
"Track A0201 skipped for pickup {PickupNo}: {Reason}",
bagNo,
trackResult.SkipReason);
return result;
}
if (!trackResult.Success)
{
result.Success = false;
result.FailStep = "TrackUpsert";
result.Message = $"HTTP {trackResult.StatusCode}";
logger.LogWarning(
"Track A0201 failed for pickup {PickupNo}: {StatusCode} {Body}",
bagNo,
trackResult.StatusCode,
trackResult.ResponseBody);
return result;
}
result.Success = true;
result.Message = "ok";
return result;
}
catch (Exception ex)
{
result.Success = false;
result.FailStep ??= "exception";
result.Message = ex.Message;
logger.LogWarning(ex, "Big-bag track sync failed for pickup {PickupNo}", bagNo);
return result;
}
}
private async Task<(string? Channel, string? Product)> ResolveChannelProductAsync(
string? carrierService,
CancellationToken cancellationToken)
{
var salesProductCode = carrierService?.Trim();
if (string.IsNullOrWhiteSpace(salesProductCode))
{
return (null, null);
}
var product = await salesProductRepository.GetByProductCodeAsync(salesProductCode, cancellationToken);
if (product == null)
{
return (null, salesProductCode);
}
var providers = await salesProductRepository.GetAllocationProvidersAsync(product.Id, cancellationToken);
var channelId = providers
.Where(x => x.ServiceChannelId is > 0)
.OrderBy(x => x.SerialNumber)
.Select(x => x.ServiceChannelId!.Value)
.FirstOrDefault();
if (channelId <= 0)
{
return (null, product.ProductCode);
}
var channel = await serviceChannelRepository.FirstOrDefaultAsync(x => x.Id == channelId, cancellationToken);
var channelCode = channel?.ChannelCode?.Trim();
return (
string.IsNullOrWhiteSpace(channelCode) ? null : channelCode,
product.ProductCode);
}
private static string ResolveCountry(PickupOrder pickup)
{
var isReverse = string.Equals(
PickupDirectionCodes.NormalizeOrDefault(pickup.Direction),
PickupDirectionCodes.Reverse,
StringComparison.OrdinalIgnoreCase);
if (isReverse)
{
return string.IsNullOrWhiteSpace(pickup.SenderCountryCode)
? "CN"
: pickup.SenderCountryCode.Trim().ToUpperInvariant();
}
// 正向:目的仓,仓库无国别字段,默认 CN
return "CN";
}
}