查价/下单/入库/出库统一校验与计费上下文;待排货状态及可选入库排货;运单号改为 IDE+yyMMddHHmmss;补充迁移 SQL 与测试修复。 Co-authored-by: Cursor <cursoragent@cursor.com>
148 lines
6.3 KiB
C#
148 lines
6.3 KiB
C#
using System.Text.Json;
|
||
using FluentAssertions;
|
||
using Oms.Application.Dtos.Orders;
|
||
using Oms.Application.Services.Orders;
|
||
using Oms.Domain.ChannelsAggregate.Entities;
|
||
using Oms.Domain.OrdersAggregate.Consts;
|
||
using Oms.Domain.OrdersAggregate.Entities;
|
||
using Oms.Tests.Support;
|
||
using Xunit.Abstractions;
|
||
|
||
namespace Oms.Tests;
|
||
|
||
/// <summary>
|
||
/// 模拟小包订单预报计费全流程,便于在 ParcelBillingCalculator 打断点调试。
|
||
/// F5 选「调试:模拟订单计费」或对本类测试方法 Debug Test。
|
||
/// </summary>
|
||
public class ParcelBillingCalculatorDemoTests
|
||
{
|
||
private readonly ITestOutputHelper _output;
|
||
|
||
public ParcelBillingCalculatorDemoTests(ITestOutputHelper output) => _output = output;
|
||
|
||
/// <summary>
|
||
/// 模拟一单美国美西普货小包:30×20×10cm、实重 1.5kg,走计重→进位→应收/应付算价。
|
||
/// </summary>
|
||
[Fact]
|
||
public void SimulateParcelOrderBilling_FullPipeline_ShouldMatchDesignDoc()
|
||
{
|
||
// —— 1. 模拟 OrderParcelCreateInput 件重尺 ——
|
||
var orderInput = new
|
||
{
|
||
RequestId = "demo-req-001",
|
||
ClientNo = "CLIENT-DEMO-001",
|
||
ProductCode = "US-EXPRESS-01",
|
||
ChannelCode = "CH-US-WEST",
|
||
CargoAttribute = "普货",
|
||
Length = 30M,
|
||
Width = 20M,
|
||
Height = 10M,
|
||
ActualWeight = 1.5M,
|
||
Destination = new { CountryCode = "US", Province = "CA", PostalCode = "90001" }
|
||
};
|
||
|
||
_output.WriteLine("=== 模拟订单入参 ===");
|
||
_output.WriteLine(JsonSerializer.Serialize(orderInput, new JsonSerializerOptions { WriteIndented = true }));
|
||
|
||
var quoteContext = new ParcelQuoteContext
|
||
{
|
||
CountryCode = orderInput.Destination.CountryCode,
|
||
Province = orderInput.Destination.Province,
|
||
PostalCode = orderInput.Destination.PostalCode,
|
||
CargoAttribute = ParcelBillingCalculator.MapCargoTypeToAttribute(orderInput.CargoAttribute),
|
||
Length = orderInput.Length,
|
||
Width = orderInput.Width,
|
||
Height = orderInput.Height,
|
||
ActualWeight = orderInput.ActualWeight
|
||
};
|
||
|
||
// —— 2. KPI 分区匹配(美国 90001 → 美西)——
|
||
var kpiRule = new KpiAreaInfo
|
||
{
|
||
Id = 1,
|
||
CountryCode = "US",
|
||
LeftPostcode = "90000",
|
||
RightPostcode = "96199"
|
||
};
|
||
var kpiArea = new KpiArea { Id = 1, Name = "美西分区" };
|
||
var zoneMatched = ParcelBillingCalculator.IsAreaMatched(
|
||
kpiRule, quoteContext.CountryCode, quoteContext.Province, quoteContext.City, quoteContext.PostalCode);
|
||
var zoneKeys = ParcelBillingCalculator.BuildPriceZoneKeys(kpiRule, kpiArea);
|
||
var zoneName = ParcelBillingCalculator.ResolveZoneName(kpiRule, kpiArea) ?? "美西";
|
||
|
||
zoneMatched.Should().BeTrue($"邮编 {quoteContext.PostalCode} 应命中美西分区");
|
||
_output.WriteLine($"分区匹配: {zoneMatched}, 分区名: {zoneName}, 分区键: {string.Join(',', zoneKeys)}");
|
||
|
||
// —— 3. 计重(可在 ParcelBillingCalculator.CalculateRawBillableWeight 打断点)——
|
||
var forecastWeight = ParcelBillingCalculator.BuildForecastWeight(quoteContext);
|
||
var weightRules = new[] { ParcelBillingTestData.GwRule() };
|
||
var (volumetricWeight, rawBillable) =
|
||
ParcelBillingCalculator.CalculateRawBillableWeight(forecastWeight, weightRules);
|
||
var roundingRules = new[] { ParcelBillingTestData.RoundingRange(0, 30, "0.5") };
|
||
var chargeableWeight = ParcelBillingCalculator.ApplyRounding(rawBillable, roundingRules);
|
||
|
||
_output.WriteLine($"材积重: {volumetricWeight} kg, 原始计费重: {rawBillable} kg, 进位后计费重: {chargeableWeight} kg");
|
||
|
||
chargeableWeight.Should().Be(1.5M);
|
||
|
||
// —— 4. 应收(卖价)——
|
||
var (priceVersion, effectiveTime) = ParcelBillingTestData.SnapshotMeta();
|
||
var receivableSnapshot = ParcelBillingTestData.BuildReceivableSnapshot(
|
||
ParcelBillingTestData.ReceivablePriceItem(
|
||
1, "freight",
|
||
ParcelBillingTestData.ContinueFreightDetail()),
|
||
ParcelBillingTestData.ReceivablePriceItem(
|
||
1, "操作费",
|
||
ParcelBillingTestData.FixedPriceDetail(5M)));
|
||
|
||
var receivableFees = ParcelBillingCalculator.CalculateForecastReceivableFees(
|
||
receivableSnapshot,
|
||
priceVersion,
|
||
effectiveTime,
|
||
chargeableWeight,
|
||
new[] { "美西" },
|
||
quoteContext.CargoAttribute,
|
||
null);
|
||
|
||
var receivableSummary = ParcelBillingCalculator.SummarizeFees(receivableFees);
|
||
|
||
_output.WriteLine("=== 应收费用 ===");
|
||
foreach (var fee in receivableFees)
|
||
{
|
||
_output.WriteLine($" {fee.CostItem}: {fee.Amount} {fee.Currency} ({fee.ChargingStage})");
|
||
}
|
||
_output.WriteLine($"应收合计: {receivableSummary.TotalCost} (运费 {receivableSummary.Freight} + 杂费 {receivableSummary.OtherFees})");
|
||
|
||
receivableSummary.Freight.Should().Be(24M); // 1.5kg 续单价:首重18 + 续重6
|
||
receivableSummary.TotalCost.Should().Be(29M);
|
||
|
||
// —— 5. 应付(买价)——
|
||
var payablePrices = ParcelBillingTestData.BuildPayablePrices(
|
||
ParcelBillingTestData.PayablePriceItem(
|
||
1, "freight",
|
||
ParcelBillingTestData.ContinueFreightDetail(
|
||
firstWeight: 1M, firstPrice: 12M, continueUnit: 0.5M, continuePrice: 4M)));
|
||
|
||
var payableFees = ParcelBillingCalculator.CalculateForecastPayableFees(
|
||
payablePrices,
|
||
priceVersion,
|
||
effectiveTime,
|
||
chargeableWeight,
|
||
"美西",
|
||
quoteContext.CargoAttribute);
|
||
|
||
var payableSummary = ParcelBillingCalculator.SummarizeFees(payableFees);
|
||
|
||
_output.WriteLine("=== 应付费用 ===");
|
||
foreach (var fee in payableFees)
|
||
{
|
||
_output.WriteLine($" {fee.CostItem}: {fee.Amount} {fee.Currency}");
|
||
}
|
||
_output.WriteLine($"应付合计: {payableSummary.TotalCost}");
|
||
|
||
payableSummary.Freight.Should().Be(16M); // 买价:12 + 4
|
||
|
||
_output.WriteLine($"毛利(应收-应付运费): {receivableSummary.Freight - payableSummary.Freight}");
|
||
}
|
||
}
|