admin/src/Oms.Application/Services/Orders/PlatformStoreOrderFulfillmentService.cs
zzh 7a611f8cf7
All checks were successful
Build and Deploy / deploy (push) Successful in 1m1s
fix: TikTok 准备发货改为真实 shipPackage,并回写面单状态
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 17:31:19 +08:00

471 lines
18 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 Fake.Application;
using Oms.Application.Dtos.PlatformStore;
using Oms.Application.Integrations.PlatformShop;
using Oms.Application.Shared;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.WarehouseAggregate.Consts;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 店铺订单:拉单 / 同步状态 / RTS / 面单。
/// </summary>
internal sealed class PlatformStoreOrderFulfillmentService(
ISalesStoreRepository storeRepository,
ISalesStoreOrderRepository storeOrderRepository,
ISalesStoreOrderLineRepository storeOrderLineRepository,
IPlatformShopFulfillmentClient fulfillmentClient)
{
public async Task<PlatformStoreOrderBatchActionOutput> PullOrdersAsync(
int customerId,
PlatformStoreOrderPullInput input)
{
if (input.StoreIds.Count == 0)
{
throw new BusinessException("请选择要拉单的店铺");
}
var stores = await storeRepository.GetListAsync(x =>
x.CustomerId == customerId && input.StoreIds.Contains(x.Id));
if (stores.Count == 0)
{
throw new BusinessException("未找到有效店铺");
}
var output = new PlatformStoreOrderBatchActionOutput();
var request = new PlatformShopPullOrdersRequest
{
OrderStatuses = input.OrderStatuses ?? [],
TimeStart = input.TimeStart,
TimeEnd = input.TimeEnd
};
foreach (var store in stores)
{
try
{
var pull = await fulfillmentClient.PullOrdersAsync(store, request);
if (!pull.Success)
{
output.FailCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = false,
Message = pull.Message
});
continue;
}
var upserted = 0;
foreach (var pulled in pull.Orders)
{
await UpsertPulledOrderAsync(customerId, store, pulled);
upserted++;
}
output.SuccessCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = true,
Message = pull.Message,
AffectedCount = upserted
});
}
catch (Exception ex)
{
output.FailCount++;
output.Items.Add(new PlatformStoreOrderBatchActionItemOutput
{
StoreId = store.Id,
StoreNickname = store.StoreNickname,
Success = false,
Message = ex.Message
});
}
}
output.Message = $"拉单完成:成功 {output.SuccessCount} 店,失败 {output.FailCount} 店";
return output;
}
public async Task<PlatformStoreOrderBatchActionOutput> SyncStatusAsync(
int? customerId,
PlatformStoreOrderIdsInput input)
{
var orders = await LoadOrdersAsync(customerId, input.Ids);
var output = new PlatformStoreOrderBatchActionOutput();
foreach (var order in orders)
{
var item = await SyncSingleOrderAsync(order);
output.Items.Add(item);
if (item.Success) output.SuccessCount++;
else output.FailCount++;
}
output.Message = $"同步完成:成功 {output.SuccessCount} 单,失败 {output.FailCount} 单";
return output;
}
public async Task<PlatformStoreOrderBatchActionOutput> ReadyToShipAsync(
int? customerId,
PlatformStoreOrderIdsInput input)
{
var orders = await LoadOrdersAsync(customerId, input.Ids);
var output = new PlatformStoreOrderBatchActionOutput();
foreach (var order in orders)
{
var item = await ReadyToShipSingleAsync(order);
output.Items.Add(item);
if (item.Success) output.SuccessCount++;
else output.FailCount++;
}
output.Message = $"RTS 完成:成功 {output.SuccessCount} 单,失败 {output.FailCount} 单";
return output;
}
public async Task<PlatformStoreOrderActionOutput> FetchLabelAsync(
int? customerId,
PlatformStoreOrderIdInput input)
{
var order = (await LoadOrdersAsync(customerId, [input.Id])).FirstOrDefault()
?? throw new BusinessException("店铺订单不存在");
if (order.IsSuspended)
{
throw new BusinessException("搁置订单不可拉取面单");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId)
?? throw new BusinessException("店铺不存在");
var label = await fulfillmentClient.FetchShippingLabelAsync(store, order.PlatformOrderNo);
if (!label.Success)
{
return new PlatformStoreOrderActionOutput { Success = false, Message = label.Message };
}
if (!string.IsNullOrWhiteSpace(label.ShippingLabelNo))
{
order.ShippingLabelNo = label.ShippingLabelNo;
}
if (!string.IsNullOrWhiteSpace(label.ShippingLabelUrl))
{
order.ShippingLabelUrl = label.ShippingLabelUrl;
}
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
return new PlatformStoreOrderActionOutput
{
Success = true,
Message = label.Message,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
private async Task<PlatformStoreOrderBatchActionItemOutput> SyncSingleOrderAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
return FailItem(order, "搁置订单不可同步");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId);
if (store == null)
{
return FailItem(order, "店铺不存在");
}
try
{
var detail = await fulfillmentClient.FetchOrderDetailAsync(store, order.PlatformOrderNo);
if (detail != null)
{
await UpsertPulledOrderAsync(order.CustomerId, store, detail);
var refreshed = await storeOrderRepository.FirstOrDefaultAsync(x => x.Id == order.Id) ?? order;
return new PlatformStoreOrderBatchActionItemOutput
{
Id = refreshed.Id,
PlatformOrderNo = refreshed.PlatformOrderNo,
Success = true,
Message = "同步成功",
FulfillStatus = refreshed.FulfillStatus,
ShippingLabelNo = refreshed.ShippingLabelNo,
ShippingLabelUrl = refreshed.ShippingLabelUrl
};
}
var result = await fulfillmentClient.FetchOrderStatusAsync(store, order.PlatformOrderNo);
if (!result.Success)
{
return FailItem(order, result.Message);
}
ApplyStatusResult(order, result);
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
return new PlatformStoreOrderBatchActionItemOutput
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = true,
Message = result.Message,
FulfillStatus = order.FulfillStatus,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
catch (Exception ex)
{
return FailItem(order, ex.Message);
}
}
private async Task<PlatformStoreOrderBatchActionItemOutput> ReadyToShipSingleAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
return FailItem(order, "搁置订单不可 RTS");
}
if (order.FulfillStatus is SalesStoreFulfillStatusCodes.Shipped or SalesStoreFulfillStatusCodes.Completed)
{
return FailItem(order, "订单已发货,不可 RTS");
}
if (string.Equals(order.PlatformCode, "temu", StringComparison.OrdinalIgnoreCase))
{
return FailItem(order, "Temu 不支持 RTS");
}
var store = await storeRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId);
if (store == null)
{
return FailItem(order, "店铺不存在");
}
try
{
var result = await fulfillmentClient.ReadyToShipAsync(store, order.PlatformOrderNo);
if (!result.Success)
{
return FailItem(order, result.Message);
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatus))
{
order.PlatformStatus = result.PlatformStatus;
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatusLabel))
{
order.PlatformStatusLabel = result.PlatformStatusLabel;
}
if (!string.IsNullOrWhiteSpace(result.FulfillStatus))
{
order.FulfillStatus = result.FulfillStatus;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelNo))
{
order.ShippingLabelNo = result.ShippingLabelNo;
}
else if (result.LabelPending)
{
// 对齐 WMS:准备发货后先标记面单待获取
order.ShippingLabelNo = "系统获取中";
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelUrl))
{
order.ShippingLabelUrl = result.ShippingLabelUrl;
}
order.LastSyncTime = DateTime.Now;
await storeOrderRepository.UpdateAsync(order);
// TikTok:发货后再拉详情,回写运单号等;面单未就绪时保留「系统获取中」
if (string.Equals(order.PlatformCode, "tiktok", StringComparison.OrdinalIgnoreCase))
{
try
{
var detail = await fulfillmentClient.FetchOrderDetailAsync(store, order.PlatformOrderNo);
if (detail != null)
{
if (result.LabelPending && string.IsNullOrWhiteSpace(detail.ShippingLabelUrl))
{
detail.ShippingLabelUrl = "系统获取中";
detail.ShippingLabelNo ??= "系统获取中";
}
detail.PlatformStatus ??= "AWAITING_COLLECTION";
detail.PlatformStatusLabel ??= detail.PlatformStatus;
detail.FulfillStatus = string.IsNullOrWhiteSpace(detail.FulfillStatus)
? "toShip"
: detail.FulfillStatus;
await UpsertPulledOrderAsync(order.CustomerId, store, detail);
order = await storeOrderRepository.FirstOrDefaultAsync(x => x.Id == order.Id) ?? order;
}
}
catch
{
// 详情回写失败不影响 RTS 成功结果
}
}
return new PlatformStoreOrderBatchActionItemOutput
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = true,
Message = result.Message,
FulfillStatus = order.FulfillStatus,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl
};
}
catch (Exception ex)
{
return FailItem(order, ex.Message);
}
}
private async Task UpsertPulledOrderAsync(int customerId, SalesStore store, PlatformShopPulledOrder pulled)
{
var entity = await storeOrderRepository.FirstOrDefaultAsync(x =>
x.StoreId == store.Id && x.PlatformOrderNo == pulled.PlatformOrderNo);
var now = DateTime.Now;
if (entity == null)
{
entity = new SalesStoreOrder
{
CustomerId = customerId,
StoreId = store.Id,
PlatformCode = store.PlatformCode,
PlatformOrderNo = pulled.PlatformOrderNo,
FulfillStatus = pulled.FulfillStatus
};
}
entity.OrderCountry = pulled.OrderCountry ?? entity.OrderCountry;
if (!string.IsNullOrWhiteSpace(pulled.RecipientName)) entity.RecipientName = pulled.RecipientName;
if (!string.IsNullOrWhiteSpace(pulled.RecipientPhone)) entity.RecipientPhone = pulled.RecipientPhone;
if (!string.IsNullOrWhiteSpace(pulled.RecipientEmail)) entity.RecipientEmail = pulled.RecipientEmail;
if (!string.IsNullOrWhiteSpace(pulled.RecipientCountry)) entity.RecipientCountry = pulled.RecipientCountry;
if (!string.IsNullOrWhiteSpace(pulled.RecipientProvince)) entity.RecipientProvince = pulled.RecipientProvince;
if (!string.IsNullOrWhiteSpace(pulled.RecipientCity)) entity.RecipientCity = pulled.RecipientCity;
if (!string.IsNullOrWhiteSpace(pulled.RecipientZipCode)) entity.RecipientZipCode = pulled.RecipientZipCode;
if (!string.IsNullOrWhiteSpace(pulled.RecipientAddress1)) entity.RecipientAddress1 = pulled.RecipientAddress1;
if (!string.IsNullOrWhiteSpace(pulled.RecipientAddress2)) entity.RecipientAddress2 = pulled.RecipientAddress2;
entity.ItemCount = pulled.ItemCount > 0 ? pulled.ItemCount : entity.ItemCount;
entity.TotalAmount = pulled.TotalAmount ?? entity.TotalAmount;
entity.Currency = pulled.Currency ?? entity.Currency;
entity.PlatformOrderTime = pulled.PlatformOrderTime ?? entity.PlatformOrderTime;
entity.PlatformStatus = pulled.PlatformStatus ?? entity.PlatformStatus;
entity.PlatformStatusLabel = pulled.PlatformStatusLabel ?? entity.PlatformStatusLabel;
entity.FulfillStatus = pulled.FulfillStatus;
entity.ShippingLabelNo = pulled.ShippingLabelNo ?? entity.ShippingLabelNo;
entity.ShippingLabelUrl = pulled.ShippingLabelUrl ?? entity.ShippingLabelUrl;
entity.IsMultiLabel = pulled.IsMultiLabel || entity.IsMultiLabel;
entity.PlatformExtraJson = pulled.ExtraJson ?? entity.PlatformExtraJson;
entity.LastSyncTime = now;
PlatformStoreOrderParcelBuilder.TryFillRecipientFields(entity, pulled.ExtraJson);
if (entity.Id > 0)
{
await storeOrderRepository.UpdateAsync(entity);
}
else
{
entity = await storeOrderRepository.InsertAsync(entity);
}
if (pulled.Lines.Count == 0)
{
return;
}
var existingLines = await storeOrderLineRepository.GetListAsync(x => x.StoreOrderId == entity.Id);
foreach (var line in existingLines)
{
await storeOrderLineRepository.DeleteAsync(line);
}
foreach (var line in pulled.Lines)
{
await storeOrderLineRepository.InsertAsync(new SalesStoreOrderLine
{
StoreOrderId = entity.Id,
PlatformLineId = line.PlatformLineId,
PlatformSku = line.PlatformSku,
ProductName = line.ProductName,
ProductImageUrl = line.ProductImageUrl,
VariantText = line.VariantText,
Quantity = line.Quantity > 0 ? line.Quantity : 1,
UnitPrice = line.UnitPrice,
Currency = line.Currency
});
}
}
private static void ApplyStatusResult(SalesStoreOrder order, PlatformShopOrderStatusResult result)
{
if (!string.IsNullOrWhiteSpace(result.PlatformStatus))
{
order.PlatformStatus = result.PlatformStatus;
}
if (!string.IsNullOrWhiteSpace(result.PlatformStatusLabel))
{
order.PlatformStatusLabel = result.PlatformStatusLabel;
}
if (!string.IsNullOrWhiteSpace(result.FulfillStatus))
{
order.FulfillStatus = result.FulfillStatus;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelNo))
{
order.ShippingLabelNo = result.ShippingLabelNo;
}
if (!string.IsNullOrWhiteSpace(result.ShippingLabelUrl))
{
order.ShippingLabelUrl = result.ShippingLabelUrl;
}
}
private async Task<List<SalesStoreOrder>> LoadOrdersAsync(int? customerId, List<int> ids)
{
if (ids.Count == 0)
{
throw new BusinessException("请选择订单");
}
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
return await storeOrderRepository.GetListAsync(x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue) && ids.Contains(x.Id));
}
private static PlatformStoreOrderBatchActionItemOutput FailItem(SalesStoreOrder order, string message) =>
new()
{
Id = order.Id,
PlatformOrderNo = order.PlatformOrderNo,
Success = false,
Message = message
};
}