admin/src/Oms.Tests/ParcelBillingCalculatorDemoTests.cs
zzh 3d5eaa223a feat: 小包全链路加固、待排货与运单号 IDE 前缀
查价/下单/入库/出库统一校验与计费上下文;待排货状态及可选入库排货;运单号改为 IDE+yyMMddHHmmss;补充迁移 SQL 与测试修复。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 19:00:55 +08:00

148 lines
6.3 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.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}");
}
}