admin/src/Oms.Application/Services/Orders/PlatformStoreOrderService.cs
zzh 9a48ec1a63 feat: 店铺订单拉单同步、RTS 与重新获取面单
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 11:38:06 +08:00

738 lines
30 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 System.Linq.Expressions;
using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Orders;
using Oms.Application.Dtos.PlatformStore;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.CustomersAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.Shared.Enums;
using Oms.Domain.SystemAggregate.Repositories;
using Oms.Domain.WarehouseAggregate.Consts;
using Oms.Domain.WarehouseAggregate.Repositories;
namespace Oms.Application.Services.Orders;
/// <summary>
/// 店铺订单 API 服务。
/// </summary>
[ApiExplorerSettings(GroupName = "OMS-Order")]
public class PlatformStoreOrderService : ApplicationService
{
private ICustomerRepository CustomerRepository => LazyServiceProvider.GetRequiredService<ICustomerRepository>();
private ISalesStoreRepository StoreRepository => LazyServiceProvider.GetRequiredService<ISalesStoreRepository>();
private ISalesStoreOrderRepository StoreOrderRepository => LazyServiceProvider.GetRequiredService<ISalesStoreOrderRepository>();
private ISalesStoreOrderLineRepository StoreOrderLineRepository => LazyServiceProvider.GetRequiredService<ISalesStoreOrderLineRepository>();
private ICSkuRepository SkuRepository => LazyServiceProvider.GetRequiredService<ICSkuRepository>();
private ICSkuPlatformMapRepository SkuPlatformMapRepository => LazyServiceProvider.GetRequiredService<ICSkuPlatformMapRepository>();
private ICInventoryRepository InventoryRepository => LazyServiceProvider.GetRequiredService<ICInventoryRepository>();
private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService<IOrderRepository>();
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService<IWarehouseAddressRepository>();
private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService<ISalesProductRepository>();
private OrderService OrderService => LazyServiceProvider.GetRequiredService<OrderService>();
private PlatformStoreOrderFulfillmentService FulfillmentService =>
LazyServiceProvider.GetRequiredService<PlatformStoreOrderFulfillmentService>();
/// <summary>
/// 查询店铺订单列表。
/// </summary>
/// <param name="input">入参:店铺订单分页查询条件。</param>
/// <returns>店铺订单分页结果。</returns>
[HttpPost("/order/platform-store-order/list")]
public async Task<OmsPageResult<PlatformStoreOrderOutput>> GetListAsync(PlatformStoreOrderListInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var predicate = BuildListPredicate(customerId, input);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
// 避免 Fake GetPagedListAsync(sorting) 丢失 Where:total 正确但 items 返回全量
var (entities, total) = await RepositoryPageHelper.GetPageByUpdateTimeDescAsync(
StoreOrderRepository,
predicate,
pageIndex,
pageSize);
var storeIds = entities.Select(x => x.StoreId).Distinct().ToArray();
var stores = storeIds.Length == 0
? []
: await StoreRepository.GetListAsync(x => storeIds.Contains(x.Id));
var storeMap = stores.ToDictionary(x => x.Id);
var linkedOrderIds = entities.Where(x => x.LinkedOrderId is > 0).Select(x => x.LinkedOrderId!.Value).Distinct().ToArray();
var linkedOrders = linkedOrderIds.Length == 0
? []
: await OrderRepository.GetListAsync(x => linkedOrderIds.Contains(x.Id));
var linkedOrderMap = linkedOrders.ToDictionary(x => x.Id);
return new OmsPageResult<PlatformStoreOrderOutput>
{
Items = entities.Select(x =>
{
storeMap.TryGetValue(x.StoreId, out var store);
string? linkedServiceNo = null;
if (x.LinkedOrderId is > 0 && linkedOrderMap.TryGetValue(x.LinkedOrderId.Value, out var linkedOrder))
{
linkedServiceNo = linkedOrder.ServiceNo;
}
return MapOrderOutput(x, store?.StoreNickname ?? string.Empty, [], linkedServiceNo: linkedServiceNo);
}).ToList(),
Total = total
};
}
/// <summary>
/// 查询店铺订单详情。
/// </summary>
/// <param name="id">店铺订单 ID。</param>
/// <param name="customerId">客户 ID(可选)。</param>
/// <returns>店铺订单详情(含商品行)。</returns>
[HttpGet("/order/platform-store-order/detail")]
public async Task<PlatformStoreOrderOutput> GetDetailAsync([FromQuery] int id, [FromQuery] int? customerId = null)
{
var resolvedCustomerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
customerId);
var order = await GetOrderOrThrowAsync(resolvedCustomerId, id);
var store = await StoreRepository.FirstOrDefaultAsync(x => x.Id == order.StoreId);
var lines = await StoreOrderLineRepository.GetListAsync(x => x.StoreOrderId == order.Id);
var skuIds = lines.Where(x => x.MappedSkuId.HasValue).Select(x => x.MappedSkuId!.Value).Distinct().ToArray();
var skus = skuIds.Length == 0
? []
: await SkuRepository.GetListAsync(x => skuIds.Contains(x.Id));
var skuMap = skus.ToDictionary(x => x.Id);
var linkedServiceNo = await ResolveLinkedServiceNoAsync(order.LinkedOrderId);
return MapOrderOutput(order, store?.StoreNickname ?? string.Empty, lines, skuMap, linkedServiceNo);
}
/// <summary>
/// 查询店铺订单状态汇总。
/// </summary>
/// <param name="customerId">客户 ID(可选)。</param>
/// <param name="storeId">店铺 ID(可选)。</param>
/// <returns>各履约状态数量汇总。</returns>
[HttpGet("/order/platform-store-order/summary")]
public async Task<PlatformStoreOrderSummaryOutput> GetSummaryAsync([FromQuery] int? customerId = null, [FromQuery] int? storeId = null)
{
var resolvedCustomerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
customerId);
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(resolvedCustomerId);
var hasStoreFilter = storeId.HasValue;
var storeIdValue = storeId.GetValueOrDefault();
var orders = await StoreOrderRepository.GetListAsync(x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (!hasStoreFilter || x.StoreId == storeIdValue));
return new PlatformStoreOrderSummaryOutput
{
Pending = orders.Count(x => x.FulfillStatus == SalesStoreFulfillStatusCodes.Pending),
ToShip = orders.Count(x => x.FulfillStatus == SalesStoreFulfillStatusCodes.ToShip),
Shipped = orders.Count(x => x.FulfillStatus == SalesStoreFulfillStatusCodes.Shipped),
Canceled = orders.Count(x => x.FulfillStatus == SalesStoreFulfillStatusCodes.Canceled),
Suspended = orders.Count(x => x.IsSuspended),
Completed = orders.Count(x => x.FulfillStatus == SalesStoreFulfillStatusCodes.Completed)
};
}
/// <summary>
/// 从平台拉取店铺订单并入库。
/// </summary>
/// <param name="input">入参:店铺、时间范围、平台状态。</param>
/// <returns>拉单结果(按店铺汇总)。</returns>
[HttpPost("/order/platform-store-order/pull-orders")]
public async Task<PlatformStoreOrderBatchActionOutput> PullOrdersAsync(PlatformStoreOrderPullInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return await FulfillmentService.PullOrdersAsync(customerId, input);
}
/// <summary>
/// 同步店铺订单状态(从平台刷新选中订单)。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
/// <returns>同步结果(成功/失败明细)。</returns>
[HttpPost("/order/platform-store-order/sync-status")]
public async Task<PlatformStoreOrderBatchActionOutput> SyncStatusAsync(PlatformStoreOrderIdsInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return await FulfillmentService.SyncStatusAsync(customerId, input);
}
/// <summary>
/// 店铺订单 RTS 就绪发货。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
/// <returns>操作结果(成功/失败明细)。</returns>
[HttpPost("/order/platform-store-order/ready-to-ship")]
public async Task<PlatformStoreOrderBatchActionOutput> ReadyToShipAsync(PlatformStoreOrderIdsInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return await FulfillmentService.ReadyToShipAsync(customerId, input);
}
/// <summary>
/// 重新拉取店铺订单面单。
/// </summary>
/// <param name="input">入参:订单 ID。</param>
/// <returns>面单号与下载地址。</returns>
[HttpPost("/order/platform-store-order/fetch-label")]
public async Task<PlatformStoreOrderActionOutput> FetchLabelAsync(PlatformStoreOrderIdInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
return await FulfillmentService.FetchLabelAsync(customerId, input);
}
/// <summary>
/// 切换店铺订单搁置状态。
/// </summary>
/// <param name="input">入参:订单 ID 与是否搁置。</param>
[HttpPost("/order/platform-store-order/toggle-suspend")]
public async Task ToggleSuspendAsync(PlatformStoreOrderToggleSuspendInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var order = await GetOrderOrThrowAsync(customerId, input.Id);
order.IsSuspended = input.IsSuspended;
order.FulfillStatus = input.IsSuspended
? SalesStoreFulfillStatusCodes.Suspended
: SalesStoreFulfillStatusCodes.Pending;
await StoreOrderRepository.UpdateAsync(order);
}
/// <summary>
/// 删除店铺订单。
/// </summary>
/// <param name="input">入参:订单 ID 列表。</param>
[HttpPost("/order/platform-store-order/delete")]
public async Task DeleteAsync(PlatformStoreOrderIdsInput input)
{
if (input.Ids.Count == 0)
{
throw new BusinessException("请选择要删除的订单");
}
var customerId = await WarehouseCustomerHelper.ResolveListCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
var orders = await StoreOrderRepository.GetListAsync(x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue) && input.Ids.Contains(x.Id));
foreach (var order in orders)
{
var lines = await StoreOrderLineRepository.GetListAsync(x => x.StoreOrderId == order.Id);
foreach (var line in lines)
{
await StoreOrderLineRepository.DeleteAsync(line);
}
await StoreOrderRepository.DeleteAsync(order);
}
}
/// <summary>
/// 店铺订单匹配库存并创建小包单。
/// </summary>
/// <param name="input">入参:订单 ID、仓库、销售产品等。</param>
/// <returns>匹配结果与关联小包单号。</returns>
[HttpPost("/order/platform-store-order/match-inventory")]
public async Task<PlatformStoreOrderMatchInventoryOutput> MatchInventoryAsync(PlatformStoreOrderMatchInventoryInput input)
{
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var result = await MatchInventoryCoreAsync(
customerId,
input.Id,
input.WarehouseId,
input.ProductCode,
input.TrackNo,
input.Receiver);
return new PlatformStoreOrderMatchInventoryOutput
{
Success = true,
Message = $"库存匹配成功,已创建小包订单 {result.ServiceNo}",
LinkedOrderId = result.OrderId,
ServiceNo = result.ServiceNo,
ClientNo = result.ClientNo
};
}
/// <summary>
/// 批量匹配店铺订单库存并创建小包单。
/// </summary>
/// <param name="input">入参:订单 ID 列表、仓库、销售产品等。</param>
/// <returns>批量匹配结果(成功/失败明细)。</returns>
[HttpPost("/order/platform-store-order/batch-match-inventory")]
public async Task<PlatformStoreOrderBatchMatchInventoryOutput> BatchMatchInventoryAsync(
PlatformStoreOrderBatchMatchInventoryInput input)
{
if (input.Ids.Count == 0)
{
throw new BusinessException("请选择要匹配的订单");
}
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
CustomerRepository,
CurrentUser,
input.CustomerId);
var output = new PlatformStoreOrderBatchMatchInventoryOutput();
foreach (var id in input.Ids.Distinct())
{
var storeOrder = await StoreOrderRepository.FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId);
var item = new PlatformStoreOrderBatchMatchItemOutput
{
Id = id,
PlatformOrderNo = storeOrder?.PlatformOrderNo ?? id.ToString()
};
try
{
var result = await MatchInventoryCoreAsync(
customerId,
id,
input.WarehouseId,
input.ProductCode,
null,
null);
item.Success = true;
item.Message = $"已创建小包订单 {result.ServiceNo}";
item.LinkedOrderId = result.OrderId;
item.ServiceNo = result.ServiceNo;
output.SuccessCount++;
}
catch (BusinessException ex)
{
item.Success = false;
item.Message = ex.Message;
output.FailCount++;
}
catch (Exception ex)
{
item.Success = false;
item.Message = ex.Message;
output.FailCount++;
}
output.Items.Add(item);
}
return output;
}
private async Task<OrderParcelCreateOutput> MatchInventoryCoreAsync(
int customerId,
int storeOrderId,
int? warehouseId,
string? productCode,
string? trackNo,
OrderParcelReceiverInput? receiver)
{
var order = await GetOrderOrThrowAsync(customerId, storeOrderId);
await EnsureMatchableAsync(order);
var resolvedWarehouseId = warehouseId ?? order.WarehouseId;
if (!resolvedWarehouseId.HasValue || resolvedWarehouseId.Value <= 0)
{
throw new BusinessException("请指定发货仓库");
}
var warehouse = await WarehouseAddressRepository.FirstOrDefaultAsync(x => x.Id == resolvedWarehouseId.Value)
?? throw new BusinessException("仓库不存在");
var lines = await StoreOrderLineRepository.GetListAsync(x => x.StoreOrderId == order.Id && !x.IsShipExcluded);
if (lines.Count == 0)
{
throw new BusinessException("没有可发货的商品行");
}
await ResolveMappedSkuLinesAsync(customerId, order, lines);
var requiredQty = lines
.Where(x => x.MappedSkuId.HasValue)
.GroupBy(x => x.MappedSkuId!.Value)
.ToDictionary(g => g.Key, g => g.Sum(x => x.Quantity));
await EnsureInventoryAvailableAsync(customerId, resolvedWarehouseId.Value, requiredQty);
var skuIds = requiredQty.Keys.ToArray();
var skus = await SkuRepository.GetListAsync(x => x.CustomerId == customerId && skuIds.Contains(x.Id));
var skuMap = skus.ToDictionary(x => x.Id);
if (skuMap.Count != skuIds.Length)
{
throw new BusinessException("部分库内 SKU 不存在");
}
var resolvedProductCode = await ResolveProductCodeAsync(productCode);
var parcelInput = PlatformStoreOrderParcelBuilder.Build(
order,
lines,
skuMap,
warehouse,
resolvedProductCode,
resolvedWarehouseId.Value,
customerId,
receiver,
trackNo);
List<InventoryDeduction> deductions = [];
try
{
foreach (var (skuId, qty) in requiredQty)
{
deductions.AddRange(await DeductInventoryAsync(customerId, resolvedWarehouseId.Value, skuId, qty));
}
var parcelOutput = await OrderService.CreateParcelForBatchAsync(parcelInput, CurrentUser.GetId<int>());
order.WarehouseId = resolvedWarehouseId;
order.LinkedOrderId = parcelOutput.OrderId;
order.FulfillStatus = SalesStoreFulfillStatusCodes.ToShip;
order.LastSyncTime = DateTime.Now;
if (!string.IsNullOrWhiteSpace(parcelOutput.TrackNo))
{
order.ShippingLabelNo = parcelOutput.TrackNo;
}
await StoreOrderRepository.UpdateAsync(order);
return parcelOutput;
}
catch
{
await RestoreInventoryAsync(customerId, resolvedWarehouseId.Value, deductions);
throw;
}
}
private async Task EnsureMatchableAsync(SalesStoreOrder order)
{
if (order.IsSuspended)
{
throw new BusinessException("订单已搁置,不能匹配库存");
}
if (order.FulfillStatus is SalesStoreFulfillStatusCodes.Shipped or SalesStoreFulfillStatusCodes.Completed)
{
throw new BusinessException("订单已发货或完成,不能重复匹配");
}
if (order.LinkedOrderId is > 0)
{
var linked = await OrderRepository.FirstOrDefaultAsync(x => x.Id == order.LinkedOrderId.Value);
if (linked != null && linked.OrderStatus != OrderStatusCodes.Deleted)
{
throw new BusinessException($"已关联小包订单 {linked.ServiceNo},不能重复匹配");
}
}
}
private async Task ResolveMappedSkuLinesAsync(int customerId, SalesStoreOrder order, List<SalesStoreOrderLine> lines)
{
var mappings = await SkuPlatformMapRepository.GetListAsync(x => x.CustomerId == customerId && x.StoreId == order.StoreId);
var mappingDict = mappings.ToDictionary(x => x.PlatformSku, x => x, StringComparer.OrdinalIgnoreCase);
foreach (var line in lines)
{
if (line.MappedSkuId.HasValue)
{
continue;
}
if (!string.IsNullOrWhiteSpace(line.PlatformSku) && mappingDict.TryGetValue(line.PlatformSku, out var map))
{
line.MappedSkuId = map.SkuId;
await StoreOrderLineRepository.UpdateAsync(line);
continue;
}
throw new BusinessException($"平台 SKU「{line.PlatformSku}」未映射库内 SKU");
}
}
private async Task EnsureInventoryAvailableAsync(int customerId, int warehouseId, Dictionary<int, int> requiredQty)
{
foreach (var (skuId, qty) in requiredQty)
{
var total = await InventoryRepository.GetListAsync(x =>
x.CustomerId == customerId
&& x.WarehouseId == warehouseId
&& x.SkuId == skuId
&& x.Qty > 0);
var available = total.Sum(x => x.Qty);
if (available < qty)
{
throw new BusinessException($"SKU {skuId} 库存不足,需要 {qty},可用 {available}");
}
}
}
private async Task<string> ResolveProductCodeAsync(string? productCode)
{
var normalized = productCode?.Trim();
if (!string.IsNullOrWhiteSpace(normalized))
{
var product = await SalesProductRepository.GetByProductCodeAsync(normalized);
if (product == null || product.IsActive != EnableStatus.Enabled)
{
throw new BusinessException("销售产品不存在或未启用");
}
return normalized;
}
var products = await SalesProductRepository.GetListAsync(x => x.IsActive == EnableStatus.Enabled);
var first = products.OrderBy(x => x.ProductCode).FirstOrDefault();
if (first == null || string.IsNullOrWhiteSpace(first.ProductCode))
{
throw new BusinessException("请指定销售产品编码");
}
return first.ProductCode;
}
private async Task<List<InventoryDeduction>> DeductInventoryAsync(int customerId, int warehouseId, int skuId, int requiredQty)
{
var rows = await InventoryRepository.GetListAsync(x =>
x.CustomerId == customerId
&& x.WarehouseId == warehouseId
&& x.SkuId == skuId
&& x.Qty > 0);
rows = rows.OrderBy(x => x.BinLocationId).ToList();
var deductions = new List<InventoryDeduction>();
var remaining = requiredQty;
foreach (var row in rows)
{
if (remaining <= 0)
{
break;
}
var deduct = Math.Min(row.Qty, remaining);
row.Qty -= deduct;
remaining -= deduct;
await InventoryRepository.UpdateAsync(row);
deductions.Add(new InventoryDeduction(skuId, row.BinLocationId, deduct));
}
if (remaining > 0)
{
throw new BusinessException($"SKU {skuId} 库存扣减失败");
}
return deductions;
}
private async Task RestoreInventoryAsync(int customerId, int warehouseId, IReadOnlyCollection<InventoryDeduction> deductions)
{
foreach (var item in deductions)
{
var inventory = await InventoryRepository.FirstOrDefaultAsync(x =>
x.CustomerId == customerId
&& x.WarehouseId == warehouseId
&& x.SkuId == item.SkuId
&& x.BinLocationId == item.BinLocationId);
if (inventory == null)
{
inventory = new Domain.WarehouseAggregate.Entities.CInventory
{
CustomerId = customerId,
WarehouseId = warehouseId,
SkuId = item.SkuId,
BinLocationId = item.BinLocationId,
Qty = item.Qty
};
await InventoryRepository.InsertAsync(inventory);
continue;
}
inventory.Qty += item.Qty;
await InventoryRepository.UpdateAsync(inventory);
}
}
private async Task<string?> ResolveLinkedServiceNoAsync(int? linkedOrderId)
{
if (linkedOrderId is not > 0)
{
return null;
}
var linked = await OrderRepository.FirstOrDefaultAsync(x => x.Id == linkedOrderId.Value);
return linked?.ServiceNo;
}
private async Task<SalesStoreOrder> GetOrderOrThrowAsync(int? customerId, int id)
{
var entity = customerId.HasValue
? await StoreOrderRepository.FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId.Value)
: await StoreOrderRepository.FirstOrDefaultAsync(x => x.Id == id);
if (entity == null)
{
throw new BusinessException("店铺订单不存在");
}
return entity;
}
private static Expression<Func<SalesStoreOrder, bool>> BuildListPredicate(int? customerId, PlatformStoreOrderListInput input)
{
var (hasCustomerFilter, customerIdValue) = WarehouseCustomerHelper.ToCustomerFilter(customerId);
// 前端 Tab「全部」会传 all / 空;需忽略,不能按字面 FulfillStatus == "all" 过滤
var fulfillStatus = NormalizeFulfillStatusFilter(input.FulfillStatus);
var hasFulfillStatusFilter = !string.IsNullOrWhiteSpace(fulfillStatus);
var platformOrderNo = input.PlatformOrderNo?.Trim();
var hasStoreFilter = input.StoreId.HasValue;
var storeIdValue = input.StoreId.GetValueOrDefault();
var hasPlatformOrderNoFilter = !string.IsNullOrWhiteSpace(platformOrderNo);
var hasSuspendedFilter = input.IsSuspended.HasValue;
var isSuspendedValue = input.IsSuspended.GetValueOrDefault();
return hasFulfillStatusFilter
? x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (!hasStoreFilter || x.StoreId == storeIdValue)
&& x.FulfillStatus == fulfillStatus
&& (!hasSuspendedFilter || x.IsSuspended == isSuspendedValue)
&& (!hasPlatformOrderNoFilter || x.PlatformOrderNo.Contains(platformOrderNo!))
: x =>
(!hasCustomerFilter || x.CustomerId == customerIdValue)
&& (!hasStoreFilter || x.StoreId == storeIdValue)
&& (!hasSuspendedFilter || x.IsSuspended == isSuspendedValue)
&& (!hasPlatformOrderNoFilter || x.PlatformOrderNo.Contains(platformOrderNo!));
}
/// <summary>
/// 规范化履约状态筛选:空 / all 表示不按状态过滤;已知编码统一为规范值。
/// </summary>
private static string? NormalizeFulfillStatusFilter(string? status)
{
var value = status?.Trim();
if (string.IsNullOrEmpty(value)
|| value.Equals("all", StringComparison.OrdinalIgnoreCase))
{
return null;
}
if (value.Equals(SalesStoreFulfillStatusCodes.Pending, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.Pending;
}
if (value.Equals(SalesStoreFulfillStatusCodes.ToShip, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.ToShip;
}
if (value.Equals(SalesStoreFulfillStatusCodes.Canceled, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.Canceled;
}
if (value.Equals(SalesStoreFulfillStatusCodes.Suspended, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.Suspended;
}
if (value.Equals(SalesStoreFulfillStatusCodes.Shipped, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.Shipped;
}
if (value.Equals(SalesStoreFulfillStatusCodes.Completed, StringComparison.OrdinalIgnoreCase))
{
return SalesStoreFulfillStatusCodes.Completed;
}
return value;
}
private static PlatformStoreOrderOutput MapOrderOutput(
SalesStoreOrder order,
string storeNickname,
IReadOnlyCollection<SalesStoreOrderLine> lines,
IReadOnlyDictionary<int, Domain.WarehouseAggregate.Entities.CSku>? skuMap = null,
string? linkedServiceNo = null) =>
new()
{
Id = order.Id,
CustomerId = order.CustomerId,
StoreId = order.StoreId,
StoreNickname = storeNickname,
PlatformCode = order.PlatformCode,
PlatformOrderNo = order.PlatformOrderNo,
OrderCountry = order.OrderCountry,
ItemCount = order.ItemCount,
TotalAmount = order.TotalAmount,
Currency = order.Currency,
PlatformOrderTime = order.PlatformOrderTime,
FulfillStatus = order.FulfillStatus,
PlatformStatusLabel = order.PlatformStatusLabel,
ShippingLabelNo = order.ShippingLabelNo,
ShippingLabelUrl = order.ShippingLabelUrl,
IsMultiLabel = order.IsMultiLabel,
CustomTags = order.CustomTags,
IsSuspended = order.IsSuspended,
WarehouseId = order.WarehouseId,
LinkedOrderId = order.LinkedOrderId,
LinkedServiceNo = linkedServiceNo,
LastSyncTime = order.LastSyncTime,
Lines = lines.Select(line =>
{
string? mappedSkuCode = null;
if (line.MappedSkuId.HasValue && skuMap != null && skuMap.TryGetValue(line.MappedSkuId.Value, out var sku))
{
mappedSkuCode = sku.SkuCode;
}
return new PlatformStoreOrderLineOutput
{
Id = line.Id,
PlatformLineId = line.PlatformLineId,
PlatformSku = line.PlatformSku,
ProductName = line.ProductName,
ProductImageUrl = line.ProductImageUrl,
VariantText = line.VariantText,
UnitPrice = line.UnitPrice,
Quantity = line.Quantity,
Currency = line.Currency,
IsShipExcluded = line.IsShipExcluded,
MappedSkuId = line.MappedSkuId,
MappedSkuCode = mappedSkuCode
};
}).ToList()
};
private sealed record InventoryDeduction(int SkuId, int BinLocationId, int Qty);
}