admin/src/Oms.Application/Services/Orders/OrderService.OrderEdit.cs
zzh c02cb21049 feat: add pickup-order model and door-pickup linking
Support 1 pickup to N logistics orders with create/pay/schedule APIs, parcel inboundMethod auto-attach, plus allocation-trace and inbound-billing updates already in tree.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 12:22:30 +08:00

998 lines
34 KiB
C#

using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Consts;
using Oms.Domain.ChannelsAggregate.Entities;
using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.CustomersAggregate.Enums;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Services.Orders;
public partial class OrderService
{
private IServiceChannelRepository ServiceChannelRepository =>
LazyServiceProvider.GetRequiredService<IServiceChannelRepository>();
/// <summary>
/// 编辑订单详情(基本信息、发件人、收件人)。
/// </summary>
[HttpPost("/order/update")]
public async Task<OrderOutput> UpdateOrderDetailAsync(OrderUpdateInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
if (input.ClientNo != null)
{
var newClientNo = string.IsNullOrWhiteSpace(input.ClientNo) ? null : input.ClientNo.Trim();
if (!string.IsNullOrWhiteSpace(newClientNo)
&& !string.Equals(newClientNo, order.ClientNo, StringComparison.OrdinalIgnoreCase)
&& order.CustomerId.HasValue)
{
var duplicated = await OrderRepository.GetByCustomerAndClientNoAsync(order.CustomerId.Value, newClientNo);
if (duplicated != null && duplicated.Id != order.Id)
{
throw new BusinessException("客户单号已存在");
}
}
order.ClientNo = newClientNo;
}
if (input.TrackNo != null)
{
order.TrackNo = string.IsNullOrWhiteSpace(input.TrackNo) ? null : input.TrackNo.Trim();
}
if (input.ChannelCode != null)
{
order.ChannelCode = string.IsNullOrWhiteSpace(input.ChannelCode) ? null : input.ChannelCode.Trim();
}
if (input.CargoAttribute != null)
{
order.CargoAttribute = input.CargoAttribute.Trim();
}
if (input.WarehouseId.HasValue)
{
order.WarehouseId = input.WarehouseId.Value > 0 ? input.WarehouseId : null;
}
if (input.IsCod.HasValue)
{
order.IsCod = input.IsCod.Value ? (byte)1 : (byte)0;
}
if (input.CodCurrency != null)
{
order.CodCurrency = input.CodCurrency;
}
if (input.CodSum.HasValue)
{
order.CodSum = input.CodSum;
}
if (input.DeclaredCurrency != null)
{
order.DeclaredCurrency = input.DeclaredCurrency;
}
if (input.DeclaredSum.HasValue)
{
order.DeclaredSum = input.DeclaredSum;
}
if (input.Quantity.HasValue)
{
order.Quantity = input.Quantity;
}
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
if (input.Sender != null && order.SenderId.HasValue)
{
var senders = await OrderRepository.GetSendersByOrderIdsAsync([order.Id]);
var sender = senders.FirstOrDefault();
if (sender != null)
{
ApplySenderInput(sender, input.Sender);
if (input.Sender.CountryCode != null)
{
var country = await GetCountryOrThrowAsync(input.Sender.CountryCode, "发件国家不存在");
sender.Country = country.Id;
}
await OrderRepository.UpdateSenderAsync(sender);
}
}
if (input.Receiver != null && order.RecipientId.HasValue)
{
var recipients = await OrderRepository.GetRecipientsByOrderIdsAsync([order.Id]);
var recipient = recipients.FirstOrDefault();
if (recipient != null)
{
await ApplyReceiverInputAsync(order, recipient, input.Receiver);
await OrderRepository.UpdateRecipientAsync(recipient);
}
}
if (input.Items != null)
{
await OrderRepository.ReplaceDeclarationsAsync(
order.Id,
input.Items.Select(x => MapDeclarationInput(order.Id, x)));
}
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 编辑订单件重尺并重算计费重。
/// </summary>
[HttpPost("/order/weight/update")]
public async Task<OrderOutput> UpdateOrderWeightsAsync(OrderWeightUpdateInput input)
{
if (input.Weights.Count == 0)
{
throw new BusinessException("件重尺不能为空");
}
var order = await GetOrderOrThrowAsync(input.Id);
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
throw new BusinessException("订单未绑定销售产品,无法重算计费重");
}
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product == null)
{
throw new BusinessException("销售产品不存在");
}
var weights = (await OrderRepository.GetWeightsByOrderIdsAsync([order.Id]))
.OrderBy(x => x.Id)
.ToList();
if (weights.Count == 0)
{
throw new BusinessException("订单件重尺不存在");
}
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var updatedTypes = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var item in input.Weights)
{
var type = item.Type?.Trim();
if (string.IsNullOrWhiteSpace(type))
{
continue;
}
var row = weights.FirstOrDefault(x => string.Equals(x.Type, type, StringComparison.OrdinalIgnoreCase));
if (row == null)
{
throw new BusinessException($"缺少件重尺类型 {type}");
}
if (item.Length.HasValue)
{
row.Length = item.Length;
}
if (item.Width.HasValue)
{
row.Width = item.Width;
}
if (item.Height.HasValue)
{
row.Height = item.Height;
}
if (item.ActualWeight.HasValue)
{
row.ActualWeight = item.ActualWeight;
}
if (row.Length.HasValue && row.Width.HasValue && row.Height.HasValue)
{
row.Cubic = ParcelBillingCalculator.CalculateCubic(row.Length.Value, row.Width.Value, row.Height.Value);
}
await ApplyChargeableWeightAsync(product, snapshotBinding.Rules, row);
updatedTypes.Add(type);
}
if (updatedTypes.Contains(OrderWeightTypeCodes.Forecast))
{
var forecast = weights.First(x => x.Type == OrderWeightTypeCodes.Forecast);
SyncWeightRows(weights, forecast);
}
await OrderRepository.ReplaceWeightsAsync(order.Id, weights);
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 重算订单应收费用。
/// </summary>
[HttpPost("/order/fee/recalculate-receivable")]
public async Task<OrderOutput> RecalculateReceivableFeesAsync(OrderActionInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
EnsureReceivableMutable(order);
await RecalculateReceivableCoreAsync(order);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 重算订单应付费用。
/// </summary>
[HttpPost("/order/fee/recalculate-payable")]
public async Task<OrderOutput> RecalculatePayableFeesAsync(OrderActionInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
await RecalculatePayableCoreAsync(order);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 重跑入库计费(应收 wipe+upsert + 排货应付)。用于队列投递失败或待排货补救。
/// </summary>
[HttpPost("/order/fee/rerun-inbound-billing")]
public async Task<OrderOutput> RerunInboundBillingAsync(OrderActionInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
await ProcessInboundBillingForBatchAsync(order.Id);
order = await GetOrderOrThrowAsync(input.Id);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 查询排货过程:优先返回快照;无快照或 live=true 时按当前规则现算(不落库)。
/// </summary>
[HttpGet("/order/allocation-trace")]
public async Task<OrderAllocationTraceOutput> GetAllocationTraceAsync(
[FromQuery] int id,
[FromQuery] bool live = false)
{
var order = await GetOrderOrThrowAsync(id);
if (!live && !string.IsNullOrWhiteSpace(order.AllocationTraceJson))
{
try
{
var snapshot = JsonConvert.DeserializeObject<OrderAllocationTraceOutput>(order.AllocationTraceJson);
if (snapshot != null)
{
snapshot.OrderId = order.Id;
snapshot.FromSnapshot = true;
snapshot.TraceTime = order.AllocationTraceTime ?? snapshot.TraceTime;
if (string.IsNullOrWhiteSpace(snapshot.Scheme))
{
snapshot.Scheme = order.AllocationScheme;
snapshot.SchemeName = ResolveAllocationSchemeName(order.AllocationScheme);
}
snapshot.Candidates = SortAllocationCandidates(snapshot.Scheme, snapshot.Candidates ?? []);
return snapshot;
}
}
catch
{
// 快照损坏则走现算
}
}
return await BuildLiveAllocationTraceAsync(order);
}
private async Task<OrderAllocationTraceOutput> BuildLiveAllocationTraceAsync(Order order)
{
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
return new OrderAllocationTraceOutput
{
OrderId = order.Id,
FromSnapshot = false,
FailReason = "订单未绑定销售产品",
TraceTime = DateTime.Now
};
}
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product == null)
{
return new OrderAllocationTraceOutput
{
OrderId = order.Id,
FromSnapshot = false,
FailReason = "销售产品不存在",
TraceTime = DateTime.Now
};
}
try
{
var weights = await OrderRepository.GetWeightsByOrderIdsAsync([order.Id]);
var weightRow = ResolvePayableWeightRow(order, weights);
var chargeable = weightRow.ChargeableWeight ?? 0;
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var billing = await ResolveOrderBillingContextAsync(
order,
product,
chargeable,
snapshotBinding);
var (_, chargingStage) = ResolveReceivableBillingStage(order);
string? schemeOverride = null;
int? forceChannelId = null;
if (AllocationSchemeCodes.IsDesignated(order.AllocationScheme)
&& !string.IsNullOrWhiteSpace(order.ChannelCode))
{
var channel = await ServiceChannelRepository.FirstOrDefaultAsync(x =>
x.ChannelCode == order.ChannelCode.Trim());
if (channel != null)
{
schemeOverride = AllocationSchemeCodes.Designated;
forceChannelId = channel.Id;
}
}
else if (AllocationSchemeCodes.IsAutoScheme(order.AllocationScheme))
{
schemeOverride = order.AllocationScheme;
}
var evaluation = await EvaluateAllocationAsync(
order,
product,
billing.SnapshotBinding,
billing.ZoneKeys,
chargeable,
chargingStage,
schemeOverride,
forceChannelId);
var output = BuildAllocationTraceOutput(
order.Id,
evaluation,
fromSnapshot: false,
selectedChannelCode: evaluation.Selected?.Channel.ChannelCode ?? order.ChannelCode);
output.FromSnapshot = false;
if (string.IsNullOrWhiteSpace(output.FailReason)
&& !string.IsNullOrWhiteSpace(order.AllocationPendingReason)
&& evaluation.Selected == null)
{
output.FailReason = order.AllocationPendingReason;
}
return output;
}
catch (Exception ex)
{
return new OrderAllocationTraceOutput
{
OrderId = order.Id,
FromSnapshot = false,
Scheme = order.AllocationScheme,
SchemeName = ResolveAllocationSchemeName(order.AllocationScheme),
FailReason = ex is BusinessException be ? be.Message : ex.Message,
TraceTime = DateTime.Now
};
}
}
/// <summary>
/// 单票重新排货:仅重写应付与服务渠道,不动应收。
/// </summary>
[HttpPost("/order/fee/reallocate")]
public async Task<OrderOutput> ReallocateAsync([FromBody] OrderReallocateInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
await ReallocateCoreAsync(order, input.Scheme, input.ServiceChannelId);
order = await GetOrderOrThrowAsync(input.Id);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 批量重新排货(同步兜底,队列关闭时使用)。
/// </summary>
[HttpPost("/order/fee/reallocate-batch")]
public async Task<OrderBatchActionResultOutput> ReallocateBatchAsync([FromBody] OrderReallocateBatchInput input)
{
NormalizeReallocateOptions(input.Scheme, input.ServiceChannelId, out var scheme, out var channelId);
var result = new OrderBatchActionResultOutput();
var orderIds = (input.OrderIds ?? []).Where(x => x > 0).Distinct().ToList();
foreach (var orderId in orderIds)
{
try
{
var order = await GetOrderOrThrowAsync(orderId);
await ReallocateCoreAsync(order, scheme, channelId);
result.SuccessCount++;
}
catch (Exception ex)
{
result.Failures.Add(new OrderBatchActionFailureOutput
{
OrderId = orderId,
Reason = ex is BusinessException be ? be.Message : ex.Message
});
}
}
return result;
}
/// <summary>
/// 未付款订单更新附加服务并重算当前阶段应收(可选尝试重算应付)。
/// </summary>
[HttpPost("/order/additional-services/update")]
public async Task<OrderOutput> UpdateAdditionalServicesAsync(OrderAdditionalServicesUpdateInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
EnsureReceivableMutable(order);
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
throw new BusinessException("订单未绑定销售产品,无法更新附加服务");
}
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product == null)
{
throw new BusinessException("销售产品不存在");
}
EnsureAdditionalServicesAllowed(product, input.AdditionalServices);
order.AdditionalServices = SerializeAdditionalServices(input.AdditionalServices);
var weights = await OrderRepository.GetWeightsByOrderIdsAsync([order.Id]);
var (weightType, chargingStage) = ResolveReceivableBillingStage(order);
var weightRow = weights.FirstOrDefault(x => x.Type == weightType)
?? throw new BusinessException($"缺少件重尺类型 {weightType}");
if (!weightRow.ChargeableWeight.HasValue || weightRow.ChargeableWeight <= 0)
{
throw new BusinessException("计费重无效,请先完善件重尺");
}
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var billing = await ResolveOrderBillingContextAsync(
order,
product,
weightRow.ChargeableWeight ?? 0,
snapshotBinding);
if (chargingStage == OrderFeeChargingStageCodes.Inbound)
{
await OrderRepository.DeleteReceivableFeesAsync(order.Id);
await UpsertReceivableFeesAsync(order, billing, chargingStage);
}
else
{
await UpsertReceivableFeesAsync(
order,
billing,
chargingStage,
replaceExistingReceivable: true);
}
await RefreshMarketingPayableAmountAsync(order);
await TryConfirmServiceChannelAndPayableAsync(order, product, billing, chargingStage);
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
return (await MapOrderOutputsAsync([order])).First();
}
/// <summary>
/// 手动添加订单费用(应收或应付)。
/// </summary>
[HttpPost("/order/fee/add")]
public async Task<OrderOutput> AddOrderFeeAsync(OrderFeeAddInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
var type = input.Type?.Trim().ToLowerInvariant() ?? string.Empty;
if (type is not (OrderFeeTypeCodes.Receivable or OrderFeeTypeCodes.Payable))
{
throw new BusinessException("费用类型无效");
}
if (type == OrderFeeTypeCodes.Receivable)
{
EnsureReceivableMutable(order);
}
var costItem = input.CostItem?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(costItem))
{
throw new BusinessException("费用项不能为空");
}
if (input.Amount <= 0)
{
throw new BusinessException("金额必须大于0");
}
var currency = input.Currency?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(currency))
{
throw new BusinessException("币种不能为空");
}
var chargingStage = input.ChargingStage?.Trim() ?? string.Empty;
if (chargingStage is not (
OrderFeeChargingStageCodes.Forecast
or OrderFeeChargingStageCodes.Inbound
or OrderFeeChargingStageCodes.Outbound))
{
throw new BusinessException("计费阶段无效");
}
var fee = new OrderFee
{
Type = type,
CostItem = costItem,
Amount = input.Amount,
Currency = currency,
ChargingStage = chargingStage,
DownstreamProvider = string.IsNullOrWhiteSpace(input.DownstreamProvider)
? null
: input.DownstreamProvider.Trim(),
PriceNo = string.IsNullOrWhiteSpace(input.PriceNo) ? null : input.PriceNo.Trim(),
PriceEffectiveTime = input.PriceEffectiveTime,
CreateTime = DateTime.Now,
UpdateTime = DateTime.Now,
};
await OrderRepository.UpsertFeesAsync(order.Id, [fee]);
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
return (await MapOrderOutputsAsync([order])).First();
}
private async Task RecalculateReceivableCoreAsync(Order order)
{
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
throw new BusinessException("订单未绑定销售产品,无法重算应收");
}
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product == null)
{
throw new BusinessException("销售产品不存在");
}
var weights = await OrderRepository.GetWeightsByOrderIdsAsync([order.Id]);
var (weightType, chargingStage) = ResolveReceivableBillingStage(order);
var weightRow = weights.FirstOrDefault(x => x.Type == weightType)
?? throw new BusinessException($"缺少件重尺类型 {weightType}");
if (!weightRow.ChargeableWeight.HasValue || weightRow.ChargeableWeight <= 0)
{
throw new BusinessException("计费重无效,请先完善件重尺");
}
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var billing = await ResolveOrderBillingContextAsync(
order,
product,
weightRow.ChargeableWeight ?? 0,
snapshotBinding);
await UpsertReceivableFeesAsync(
order,
billing,
chargingStage,
replaceExistingReceivable: true);
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
}
private async Task RecalculatePayableCoreAsync(Order order)
{
await ReallocateCoreAsync(order, scheme: null, serviceChannelId: null, enforceReallocateGate: false);
}
/// <summary>
/// 重新排货核心:按方案/指定渠道确认服务渠道并重写应付。
/// </summary>
private async Task ReallocateCoreAsync(
Order order,
string? scheme,
int? serviceChannelId,
bool enforceReallocateGate = true)
{
string? schemeOverride = null;
int? forceChannelId = null;
if (enforceReallocateGate)
{
EnsureReallocateAllowed(order);
NormalizeReallocateOptions(scheme, serviceChannelId, out var normalizedScheme, out forceChannelId);
schemeOverride = AllocationSchemeCodes.IsDesignated(normalizedScheme)
? AllocationSchemeCodes.Designated
: normalizedScheme;
}
if (string.IsNullOrWhiteSpace(order.ProductCode))
{
throw new BusinessException("订单未绑定销售产品,无法重算应付");
}
var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
if (product == null)
{
throw new BusinessException("销售产品不存在");
}
var weights = await OrderRepository.GetWeightsByOrderIdsAsync([order.Id]);
var weightRow = ResolvePayableWeightRow(order, weights);
if (!weightRow.ChargeableWeight.HasValue || weightRow.ChargeableWeight <= 0)
{
throw new BusinessException("计费重无效,请先完善件重尺");
}
var snapshotBinding = await ResolveSnapshotBindingFromOrderAsync(product, order);
var billing = await ResolveOrderBillingContextAsync(
order,
product,
weightRow.ChargeableWeight ?? 0,
snapshotBinding);
var (_, chargingStage) = ResolveReceivableBillingStage(order);
await ConfirmServiceChannelAndPayableAsync(
order,
product,
billing,
chargingStage,
schemeOverride,
forceChannelId);
if (string.Equals(order.OrderStatus, OrderStatusCodes.PendingAllocation, StringComparison.OrdinalIgnoreCase))
{
order.OrderStatus = OrderStatusCodes.PendingPayment;
}
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
}
private static void EnsureReallocateAllowed(Order order)
{
if (order.IsPaid != 0)
{
throw new BusinessException("订单已付款,不允许重新排货");
}
if (order.OrderStatus is not (OrderStatusCodes.PendingAllocation or OrderStatusCodes.PendingPayment))
{
throw new BusinessException(
$"订单状态为「{OmsDocHelper.GetOrderStatusName(order.OrderStatus ?? string.Empty)}」,仅待排货/待支付可重新排货");
}
}
private static void NormalizeReallocateOptions(
string? scheme,
int? serviceChannelId,
out string normalizedScheme,
out int? forceServiceChannelId)
{
normalizedScheme = (scheme ?? string.Empty).Trim();
forceServiceChannelId = null;
if (AllocationSchemeCodes.IsDesignated(normalizedScheme))
{
if (serviceChannelId is not > 0)
{
throw new BusinessException("指定渠道时请选择服务渠道");
}
forceServiceChannelId = serviceChannelId;
return;
}
if (!AllocationSchemeCodes.IsAutoScheme(normalizedScheme))
{
throw new BusinessException("排货方案无效,请选择价格优先/时效优先/综合最优/指定渠道");
}
if (serviceChannelId is > 0)
{
// 自动方案忽略渠道入参,避免误传
forceServiceChannelId = null;
}
}
private static OrderWeight ResolvePayableWeightRow(Order order, IReadOnlyList<OrderWeight> weights)
{
var (_, chargingStage) = ResolveReceivableBillingStage(order);
var weightType = chargingStage switch
{
OrderFeeChargingStageCodes.Inbound => OrderWeightTypeCodes.Receive,
_ => OrderWeightTypeCodes.Downstream
};
return weights.FirstOrDefault(x => x.Type == weightType)
?? weights.FirstOrDefault(x => x.Type == OrderWeightTypeCodes.Downstream)
?? throw new BusinessException($"缺少件重尺类型 {weightType}");
}
private async Task<List<ServiceChannelPriceListOutput>> BuildChannelPriceOutputsAsync(int serviceChannelId)
{
var prices = await ServiceChannelRepository.GetPricesAsync(serviceChannelId);
var priceIds = prices.Select(x => x.Id).ToArray();
var factorMap = (await ServiceChannelRepository.GetPriceFactorsAsync(priceIds))
.GroupBy(x => x.PriceId)
.ToDictionary(x => x.Key, x => x.OrderBy(item => item.SerialNumber).ToList());
var detailMap = (await ServiceChannelRepository.GetPriceDetailsAsync(priceIds))
.GroupBy(x => x.PriceId)
.ToDictionary(x => x.Key, x => x.OrderBy(item => item.Id).ToList());
return prices.Select(price => new ServiceChannelPriceListOutput
{
Id = price.Id,
ServiceChannelId = price.ServiceChannelId,
SerialNumber = price.SerialNumber,
Status = (byte)price.Status,
PriceName = price.PriceName,
CostItem = price.CostItem,
CostUnit = price.CostUnit,
Currency = price.Currency,
ChargingCondition = price.ChargingCondition,
Factors = factorMap.TryGetValue(price.Id, out var factors)
? factors.Select(MapChannelPriceFactorOutput).ToList()
: [],
Details = detailMap.TryGetValue(price.Id, out var details)
? details.Select(MapChannelPriceDetailOutput).ToList()
: []
}).ToList();
}
private static ChannelPriceFactorOutput MapChannelPriceFactorOutput(ServiceChannelPriceFactor entity) =>
new()
{
Id = entity.Id,
PriceId = entity.PriceId,
SerialNumber = entity.SerialNumber,
Factor = entity.Factor,
FactorValues = OmsDocHelper.DeserializeJson<List<string>>(entity.FactorValues) ?? []
};
private static ChannelPriceDetailOutput MapChannelPriceDetailOutput(ServiceChannelPriceDetail entity) =>
new()
{
Id = entity.Id,
PriceId = entity.PriceId,
FactorValuesJson = OmsDocHelper.DeserializeJsonObject(entity.FactorValuesJson),
CalculationFormula = entity.CalculationFormula,
CalculatedValue = entity.CalculatedValue,
CustomerGroupValues = OmsDocHelper.DeserializeJsonObject(entity.CustomerGroupValues)
};
private static (string WeightType, string ChargingStage) ResolveReceivableBillingStage(Order order) =>
order.OrderStatus switch
{
OrderStatusCodes.Received or OrderStatusCodes.PendingAllocation or OrderStatusCodes.PendingPayment or OrderStatusCodes.PendingShip
=> (OrderWeightTypeCodes.Receive, OrderFeeChargingStageCodes.Inbound),
OrderStatusCodes.PendingOnline or OrderStatusCodes.Shipping
or OrderStatusCodes.DeliverySuccess or OrderStatusCodes.DeliveryFailed
=> (OrderWeightTypeCodes.Ship, OrderFeeChargingStageCodes.Outbound),
_ => (OrderWeightTypeCodes.Forecast, OrderFeeChargingStageCodes.Forecast)
};
private static OrderDeclaration MapDeclarationInput(int orderId, OrderParcelItemInput x)
{
return new OrderDeclaration
{
OrderId = orderId,
CnName = x.CnName,
EnName = x.EnName,
Sku = x.Sku,
Unit = x.Unit,
UnitPrice = x.UnitPrice,
UnitWeight = x.UnitWeight,
Quantity = x.Quantity,
Amount = x.Amount,
Weight = x.Weight,
HsCode = x.ExHsCode ?? x.ImHsCode,
ImHsCode = x.ImHsCode,
ExHsCode = x.ExHsCode ?? x.ImHsCode,
SkuImage = x.SkuImage,
Category = x.Category,
Brand = x.Brand,
BrandType = x.BrandType,
IsMagnetic = x.IsMagnetic ? (byte)1 : (byte)0,
Distribution = x.Distribution,
Material = x.Material,
EnMaterial = x.EnMaterial,
Purpose = x.Purpose,
Attributes = x.Attributes,
OriginCountry = x.OriginCountry,
SalesLink = x.SalesLink,
Model = x.Model,
Specifications = x.Specifications,
Comment = x.Comment
};
}
private static void ApplySenderInput(OrderSender sender, OrderParcelSenderInput input)
{
if (input.SenderName != null)
{
sender.SenderName = input.SenderName;
}
if (input.Company != null)
{
sender.Company = input.Company;
}
if (input.Phone != null)
{
sender.Phone = input.Phone;
}
if (input.TelePhone != null)
{
sender.TelePhone = input.TelePhone;
}
if (input.Email != null)
{
sender.Email = input.Email;
}
if (input.Province != null)
{
sender.Province = input.Province;
}
if (input.City != null)
{
sender.City = input.City;
}
if (input.ZipCode != null)
{
sender.ZipCode = input.ZipCode;
}
if (input.StreetAddress1 != null)
{
sender.StreetAddress1 = input.StreetAddress1;
}
if (input.StreetAddress2 != null)
{
sender.StreetAddress2 = input.StreetAddress2;
}
if (input.StreetAddress3 != null)
{
sender.StreetAddress3 = input.StreetAddress3;
}
if (input.HouseNumber != null)
{
sender.HouseNumber = input.HouseNumber;
}
}
private async Task ApplyReceiverInputAsync(Order order, OrderRecipient recipient, OrderParcelReceiverInput input)
{
if (input.RecipientsName != null)
{
recipient.RecipientsName = input.RecipientsName;
}
if (input.CompanyName != null)
{
recipient.CompanyName = input.CompanyName;
}
if (input.Phone != null)
{
recipient.Phone = input.Phone;
}
if (input.TelePhone != null)
{
recipient.TelePhone = input.TelePhone;
}
if (input.Email != null)
{
recipient.Email = input.Email;
}
if (input.Province != null)
{
recipient.Province = input.Province;
}
if (input.City != null)
{
recipient.City = input.City;
}
if (input.StreetNumber != null)
{
recipient.StreetNumber = input.StreetNumber;
}
if (input.StreetAddress1 != null)
{
recipient.StreetAddress1 = input.StreetAddress1;
}
if (input.StreetAddress2 != null)
{
recipient.StreetAddress2 = input.StreetAddress2;
}
if (input.StreetAddress3 != null)
{
recipient.StreetAddress3 = input.StreetAddress3;
}
if (input.ZipCode != null)
{
recipient.ZipCode = input.ZipCode;
}
if (input.TaxType.HasValue)
{
recipient.TaxType = input.TaxType;
}
if (input.TaxNo != null)
{
recipient.TaxNo = input.TaxNo;
}
if (input.CertificateType.HasValue)
{
recipient.CertificateType = input.CertificateType;
}
if (input.CertificateNo != null)
{
recipient.CertificateNo = input.CertificateNo;
}
if (input.Eori != null)
{
recipient.Eori = input.Eori;
}
if (input.Vat != null)
{
recipient.Vat = input.Vat;
}
if (input.CountryCode != null)
{
var country = await GetCountryOrThrowAsync(input.CountryCode, "收件国家不存在");
recipient.Country = country.Id;
order.ConsigneeCountry = country.CountryCode;
order.ConsigneeCountryName = country.ChineseName;
await OrderRepository.UpdateAsync(order);
}
}
}