新增仓储领域实体、仓储库、WarehouseService 全套接口及数据库迁移脚本; 实现店铺授权、店铺订单服务与库存匹配创建小包单;补充仓储模块 RBAC 权限种子数据。 Co-authored-by: Cursor <cursoragent@cursor.com>
613 lines
24 KiB
C#
613 lines
24 KiB
C#
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>();
|
|
|
|
[HttpPost("/order/platform-store-order/list")]
|
|
public async Task<OmsPageResult<PlatformStoreOrderOutput>> GetListAsync(PlatformStoreOrderListInput input)
|
|
{
|
|
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
input.CustomerId);
|
|
var predicate = BuildListPredicate(customerId, input);
|
|
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
|
|
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
|
|
var entities = await StoreOrderRepository.GetPagedListAsync(
|
|
predicate,
|
|
pageIndex,
|
|
pageSize,
|
|
new Dictionary<string, bool> { [nameof(SalesStoreOrder.UpdateTime)] = false, [nameof(SalesStoreOrder.Id)] = false });
|
|
var total = await StoreOrderRepository.CountAsync(predicate);
|
|
|
|
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
|
|
};
|
|
}
|
|
|
|
[HttpGet("/order/platform-store-order/detail")]
|
|
public async Task<PlatformStoreOrderOutput> GetDetailAsync([FromQuery] int id, [FromQuery] int? customerId = null)
|
|
{
|
|
var resolvedCustomerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
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);
|
|
}
|
|
|
|
[HttpGet("/order/platform-store-order/summary")]
|
|
public async Task<PlatformStoreOrderSummaryOutput> GetSummaryAsync([FromQuery] int? customerId = null, [FromQuery] int? storeId = null)
|
|
{
|
|
var resolvedCustomerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
customerId);
|
|
var orders = await StoreOrderRepository.GetListAsync(x =>
|
|
x.CustomerId == resolvedCustomerId
|
|
&& (!storeId.HasValue || x.StoreId == storeId.Value));
|
|
|
|
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)
|
|
};
|
|
}
|
|
|
|
[HttpPost("/order/platform-store-order/sync-status")]
|
|
public async Task<PlatformStoreOrderActionOutput> SyncStatusAsync(PlatformStoreOrderIdsInput input)
|
|
{
|
|
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
input.CustomerId);
|
|
|
|
return new PlatformStoreOrderActionOutput
|
|
{
|
|
Message = "平台订单同步待 SDK 接入,当前为占位实现"
|
|
};
|
|
}
|
|
|
|
[HttpPost("/order/platform-store-order/ready-to-ship")]
|
|
public async Task<PlatformStoreOrderActionOutput> ReadyToShipAsync(PlatformStoreOrderIdsInput input)
|
|
{
|
|
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
input.CustomerId);
|
|
|
|
return new PlatformStoreOrderActionOutput
|
|
{
|
|
Message = "RTS 待平台 SDK 接入,当前为占位实现"
|
|
};
|
|
}
|
|
|
|
[HttpPost("/order/platform-store-order/fetch-label")]
|
|
public async Task<PlatformStoreOrderActionOutput> FetchLabelAsync(PlatformStoreOrderIdInput input)
|
|
{
|
|
await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
input.CustomerId);
|
|
|
|
return new PlatformStoreOrderActionOutput
|
|
{
|
|
Message = "面单拉取待平台 SDK 接入,当前为占位实现"
|
|
};
|
|
}
|
|
|
|
[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);
|
|
}
|
|
|
|
[HttpPost("/order/platform-store-order/delete")]
|
|
public async Task DeleteAsync(PlatformStoreOrderIdsInput input)
|
|
{
|
|
if (input.Ids.Count == 0)
|
|
{
|
|
throw new BusinessException("请选择要删除的订单");
|
|
}
|
|
|
|
var customerId = await WarehouseCustomerHelper.ResolveRequiredCustomerIdAsync(
|
|
CustomerRepository,
|
|
CurrentUser,
|
|
input.CustomerId);
|
|
var orders = await StoreOrderRepository.GetListAsync(x => x.CustomerId == customerId && 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);
|
|
}
|
|
}
|
|
|
|
[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
|
|
};
|
|
}
|
|
|
|
[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 = await StoreOrderRepository.FirstOrDefaultAsync(x => x.Id == id && x.CustomerId == customerId);
|
|
if (entity == null)
|
|
{
|
|
throw new BusinessException("店铺订单不存在");
|
|
}
|
|
|
|
return entity;
|
|
}
|
|
|
|
private static Expression<Func<SalesStoreOrder, bool>> BuildListPredicate(int customerId, PlatformStoreOrderListInput input)
|
|
{
|
|
var fulfillStatus = input.FulfillStatus?.Trim();
|
|
var platformOrderNo = input.PlatformOrderNo?.Trim();
|
|
|
|
return x =>
|
|
x.CustomerId == customerId
|
|
&& (!input.StoreId.HasValue || x.StoreId == input.StoreId.Value)
|
|
&& (string.IsNullOrWhiteSpace(fulfillStatus) || x.FulfillStatus == fulfillStatus)
|
|
&& (!input.IsSuspended.HasValue || x.IsSuspended == input.IsSuspended.Value)
|
|
&& (string.IsNullOrWhiteSpace(platformOrderNo) || x.PlatformOrderNo.Contains(platformOrderNo));
|
|
}
|
|
|
|
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);
|
|
}
|