admin/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
zzh e2443ed6f8 fix: restore country+weight price-check estimates for postcode zones.
When rules require postal codes, still quote by same-country snapshot zones as estimated; keep blocking countries not configured at all.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 11:14:12 +08:00

348 lines
11 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 FluentAssertions;
using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders;
using Oms.Application.Services.Orders;
using Oms.Application.Shared;
using Oms.Domain.CustomersAggregate.Enums;
using Oms.Domain.OrdersAggregate.Consts;
namespace Oms.Tests;
/// <summary>
/// 验证查价卖价快照匹配:unit 公式、重量左开右闭区间。
/// </summary>
public class PriceCheckSnapshotVerificationTests
{
private static SalesProductPricePublishSnapshotDto BuildUserSnapshot() => new()
{
SalesProductId = 1,
Prices =
[
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 1,
Status = 2,
CostItem = "freight",
Details =
[
Detail(24, 1, "total", "3", "0,0.5", 11),
Detail(25, 1, "total", "3", "0.5,1", 12),
Detail(26, 1, "total", "3", "1,1.5", 13),
Detail(27, 1, "total", "3", "1.5,2", 14),
]
}),
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 2,
Status = 2,
CostItem = "handling",
Details = [Detail(28, 2, "total", "3", "0,10", 20)]
})
]
};
private static ChannelPriceDetailOutput Detail(
int id,
int priceId,
string formula,
string zone,
string weight,
decimal normalPrice) =>
new()
{
Id = id,
PriceId = priceId,
CalculationFormula = formula,
CalculatedValue = 0,
FactorValuesJson = new Dictionary<string, string>
{
["分区"] = zone,
["重量"] = weight
},
CustomerGroupValues = new Dictionary<string, decimal> { ["普通客户"] = normalPrice }
};
private static ChannelPriceDetailOutput DetailWithCargoAndWarehouse(
int id,
int priceId,
string formula,
string zone,
string weight,
string cargoType,
string warehouseId,
decimal normalPrice) =>
new()
{
Id = id,
PriceId = priceId,
CalculationFormula = formula,
CalculatedValue = 0,
FactorValuesJson = new Dictionary<string, string>
{
["分区"] = zone,
["重量"] = weight,
["货物类型"] = cargoType,
["仓库"] = warehouseId
},
CustomerGroupValues = new Dictionary<string, decimal> { ["普通客户"] = normalPrice }
};
[Fact]
public void EnglishCargoTypeFactor_ShouldMatchCargoAttributeForFreight()
{
var snapshot = new SalesProductPricePublishSnapshotDto
{
SalesProductId = 1,
Prices =
[
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 1,
Status = 2,
CostItem = "freight",
Details =
[
DetailWithCargoAndWarehouse(61, 1, "total", "3", "(0-0.5]", "general", "2", 10),
DetailWithCargoAndWarehouse(62, 1, "total", "3", "(0-0.5]", "electronics", "2", 11),
]
}),
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 2,
Status = 2,
CostItem = "handling",
Details = [Detail(44, 2, "total", "3", "(0-10]", 23)]
})
]
};
var generalFees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
snapshot,
"PRICE-TEST",
DateTime.Now,
0.214M,
["3"],
CargoAttributeCodes.General,
"普通客户",
warehouseId: 2);
generalFees.Should().HaveCount(2);
generalFees.Single(x => x.CostItem == "freight").Amount.Should().Be(10M);
generalFees.Single(x => x.CostItem == "handling").Amount.Should().Be(23M);
var summary = ParcelBillingCalculator.SummarizeQuoteFees(
ParcelBillingCalculator.CalculateQuoteReceivableFeeLines(
snapshot,
"PRICE-TEST",
DateTime.Now,
0.214M,
["3"],
CargoAttributeCodes.General,
"普通客户",
warehouseId: 2));
summary.CostFormula.Should().Be("10+23");
summary.Freight.Should().Be(10M);
summary.OtherFees.Should().Be(23M);
summary.OtherFeeList.Should().ContainSingle(x => x.Name == "操作费" && x.Amount == 23M);
var electronicsFees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
snapshot,
"PRICE-TEST",
DateTime.Now,
0.214M,
["3"],
CargoAttributeCodes.Electronics,
"普通客户",
warehouseId: 2);
electronicsFees.Single(x => x.CostItem == "freight").Amount.Should().Be(11M);
}
[Fact]
public void TryParseWeightInterval_SingleValue_ShouldBeLeftOpenRightClosedFromZero()
{
ParcelBillingCalculator.TryParseWeightInterval("0.5", out var start, out var end).Should().BeTrue();
start.Should().Be(0);
end.Should().Be(0.5M);
ParcelBillingCalculator.WeightInLeftOpenRightClosedInterval(0.5M, start, end).Should().BeTrue();
ParcelBillingCalculator.WeightInLeftOpenRightClosedInterval(0M, start, end).Should().BeFalse();
}
[Fact]
public void TryParseWeightInterval_IntervalNotation_ShouldParseLeftOpenRightClosed()
{
ParcelBillingCalculator.TryParseWeightInterval("(0-0.5]", out var start, out var end).Should().BeTrue();
start.Should().Be(0);
end.Should().Be(0.5M);
ParcelBillingCalculator.WeightInLeftOpenRightClosedInterval(0.5M, start, end).Should().BeTrue();
}
[Fact]
public void UserSnapshot_IntervalWeightFormat_Zone3_Weight05_ShouldMatchFreight11AndHandling20()
{
var snapshot = new SalesProductPricePublishSnapshotDto
{
SalesProductId = 1,
Prices =
[
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 1,
Status = 2,
CostItem = "freight",
Details =
[
Detail(34, 1, "total", "3", "(0-0.5]", 11),
Detail(35, 1, "total", "3", "(0.5-1]", 12),
]
}),
PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 2,
Status = 2,
CostItem = "handling",
Details = [Detail(38, 2, "total", "3", "(0-10]", 20)]
})
]
};
var fees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
snapshot,
"PRICE-20260626102033",
DateTime.Now,
0.5M,
["3"],
CargoAttributeCodes.General,
"普通客户");
fees.Should().HaveCount(2);
fees.Sum(x => x.Amount).Should().Be(31M);
}
[Fact]
public void UserSnapshot_Zone3_Weight05_ShouldMatchFreight11AndHandling20()
{
var fees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
BuildUserSnapshot(),
"PRICE-20260601-001",
DateTime.Now,
0.5M,
["3"],
CargoAttributeCodes.General,
"普通客户");
fees.Should().HaveCount(2);
fees.Single(x => x.CostItem == "freight").Amount.Should().Be(11M);
fees.Single(x => x.CostItem == "handling").Amount.Should().Be(20M);
fees.Sum(x => x.Amount).Should().Be(31M);
}
[Fact]
public void TryResolveZoneKeysFromPriceSnapshot_UsZip06000_ShouldMatchZone3()
{
var snapshot = new SalesProductPricePublishSnapshotDto
{
PriceAreaId = 3,
PriceAreaName = "美国分区",
PriceAreaInfos =
[
new KpiAreaSnapshotRuleDto
{
Id = 4,
CountryCode = "US",
PostcodeRange = "(50000,90000]"
},
new KpiAreaSnapshotRuleDto
{
Id = 3,
CountryCode = "US",
PostcodeRange = "(00000,50000]"
}
]
};
var keys = ParcelBillingCalculator.TryResolveZoneKeysFromPriceSnapshot(
snapshot,
"US",
null,
null,
"06000");
keys.Should().NotBeNull();
keys!.Should().Contain("3");
}
[Fact]
public void ExplainQuotePriceMiss_ZoneMismatch_ShouldNameEachPriceItem()
{
var reason = ParcelBillingCalculator.ExplainQuotePriceMiss(
BuildUserSnapshot(),
0.5M,
["默认分区"],
CargoAttributeCodes.General,
"普通客户");
reason.Should().Contain("分区不匹配");
reason.Should().Contain("明细分区=[3]");
reason.Should().Contain("解析分区键=[默认分区]");
reason.Should().Contain("freight");
reason.Should().Contain("handling");
}
[Fact]
public void UserSnapshot_WrongZone_ShouldReturnNoFees()
{
var fees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
BuildUserSnapshot(),
"PRICE-20260601-001",
DateTime.Now,
0.5M,
["99"],
CargoAttributeCodes.General,
"普通客户");
fees.Should().BeEmpty();
}
[Fact]
public void CountryEstimate_WhenCountryConfiguredWithPostcodeRules_ShouldReturnDetailZones()
{
var snapshot = BuildUserSnapshot();
snapshot.PriceAreaInfos =
[
new KpiAreaSnapshotRuleDto
{
Id = 11,
CountryCode = "US",
PostcodeRange = "(90000,99999]"
}
];
// 精确匹配(无邮编)应失败
ParcelBillingCalculator.TryResolveZoneKeysFromPriceSnapshot(snapshot, "US", null, null, null)
.Should().BeNull();
// 国家估算应返回卖价明细分区键
ParcelBillingCalculator.CollectCountryEstimateZoneKeys(snapshot, "US")
.Should().Contain(["3"]);
}
[Fact]
public void CountryEstimate_WhenCountryNotConfigured_ShouldReturnEmpty()
{
var snapshot = BuildUserSnapshot();
snapshot.PriceAreaInfos =
[
new KpiAreaSnapshotRuleDto
{
Id = 11,
CountryCode = "US",
PostcodeRange = "(90000,99999]"
}
];
ParcelBillingCalculator.CollectCountryEstimateZoneKeys(snapshot, "DE")
.Should().BeEmpty();
}
}