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;
///
/// 店铺订单:拉单 / 同步状态 / RTS / 面单。
///
internal sealed class PlatformStoreOrderFulfillmentService(
ISalesStoreRepository storeRepository,
ISalesStoreOrderRepository storeOrderRepository,
ISalesStoreOrderLineRepository storeOrderLineRepository,
IPlatformShopFulfillmentClient fulfillmentClient)
{
public async Task 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 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 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 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 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 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> LoadOrdersAsync(int? customerId, List 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
};
}