fix: 查价分区键从国家+邮编解析,移除默认分区兜底

PriceAreaId 优先取产品表并从规则快照回退;未命中 t_kpi_area_info 时在 zone 步骤返回明确原因,避免与卖价明细分区键不匹配。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-06-26 10:31:52 +08:00
parent 148caae1e7
commit 94a503afb3
3 changed files with 92 additions and 5 deletions

View File

@ -88,6 +88,7 @@ public partial class OrderService
var zoneKeys = await ResolvePriceZoneKeysAsync(
product,
snapshotBinding.Rules,
country.CountryCode,
input.Receiver?.Province,
input.Receiver?.City,
@ -408,31 +409,55 @@ public partial class OrderService
zipCode));
}
private static int? ResolveEffectivePriceAreaId(
SalesProduct product,
SalesProductRulesPublishSnapshotDto? rulesSnapshot)
{
if (product.PriceAreaId is > 0)
{
return product.PriceAreaId;
}
if (rulesSnapshot?.Product?.PriceAreaId is > 0)
{
return rulesSnapshot.Product.PriceAreaId;
}
return null;
}
private async Task<IReadOnlyList<string>?> ResolvePriceZoneKeysAsync(
SalesProduct product,
SalesProductRulesPublishSnapshotDto? rulesSnapshot,
string countryCode,
string? province,
string? city,
string? zipCode)
{
if (!product.PriceAreaId.HasValue || product.PriceAreaId <= 0)
var priceAreaId = ResolveEffectivePriceAreaId(product, rulesSnapshot);
if (!priceAreaId.HasValue)
{
return new[] { "默认分区" };
return null;
}
var area = await KpiAreaRepository.FirstOrDefaultAsync(x => x.Id == product.PriceAreaId.Value);
var area = await KpiAreaRepository.FirstOrDefaultAsync(x => x.Id == priceAreaId.Value);
if (area == null)
{
return null;
}
var rules = await KpiAreaRepository.GetAllInfosByAreaIdAsync(product.PriceAreaId.Value);
var rules = await KpiAreaRepository.GetAllInfosByAreaIdAsync(priceAreaId.Value);
var matched = rules.FirstOrDefault(rule => ParcelBillingCalculator.IsAreaMatched(
rule,
countryCode,
province,
city,
zipCode));
if (matched == null)
{
return null;
}
var keys = ParcelBillingCalculator.BuildPriceZoneKeys(matched, area);
return keys.Count > 0 ? keys : null;
}

View File

@ -217,8 +217,18 @@ public partial class OrderService
return PriceCheckFailure(product, "weight", ex.Message);
}
var priceAreaId = ResolveEffectivePriceAreaId(product, snapshotBinding.Rules);
if (!priceAreaId.HasValue)
{
return PriceCheckFailure(
product,
"zone",
"产品未配置价格分区(PriceAreaId),无法根据国家+邮编匹配卖价明细中的分区键");
}
var zoneKeys = await ResolvePriceZoneKeysAsync(
product,
snapshotBinding.Rules,
context.CountryCode,
context.Province,
context.City,
@ -228,7 +238,8 @@ public partial class OrderService
return PriceCheckFailure(
product,
"zone",
$"目的国 {context.CountryCode} 邮编 {FormatPostalCode(context.PostalCode)} 未匹配产品价格分区(请检查 PriceAreaId 及分区规则)");
$"目的国 {context.CountryCode} 邮编 {FormatPostalCode(context.PostalCode)} 未命中价格分区规则(PriceAreaId={priceAreaId})," +
$"卖价明细分区键(如 3) 无法解析,请检查 t_kpi_area_info 国家/邮编配置");
}
var chargeableWeight = forecast.ChargeableWeight ?? 0;

View File

@ -73,6 +73,57 @@ public class PriceCheckSnapshotVerificationTests
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 =
[
new SalesProductPriceListOutput
{
Id = 1,
Status = 1,
CostItem = "freight",
Details =
[
Detail(34, 1, "unit", "3", "(0-0.5]", 11),
Detail(35, 1, "unit", "3", "(0.5-1]", 12),
]
},
new SalesProductPriceListOutput
{
Id = 2,
Status = 1,
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,
CustomerGroup.Normal);
fees.Should().HaveCount(2);
fees.Sum(x => x.Amount).Should().Be(31M);
}
[Fact]
public void UserSnapshot_Zone3_Weight05_ShouldMatchFreight11AndHandling20()
{