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( var zoneKeys = await ResolvePriceZoneKeysAsync(
product, product,
snapshotBinding.Rules,
country.CountryCode, country.CountryCode,
input.Receiver?.Province, input.Receiver?.Province,
input.Receiver?.City, input.Receiver?.City,
@ -408,31 +409,55 @@ public partial class OrderService
zipCode)); 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( private async Task<IReadOnlyList<string>?> ResolvePriceZoneKeysAsync(
SalesProduct product, SalesProduct product,
SalesProductRulesPublishSnapshotDto? rulesSnapshot,
string countryCode, string countryCode,
string? province, string? province,
string? city, string? city,
string? zipCode) 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) if (area == null)
{ {
return 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( var matched = rules.FirstOrDefault(rule => ParcelBillingCalculator.IsAreaMatched(
rule, rule,
countryCode, countryCode,
province, province,
city, city,
zipCode)); zipCode));
if (matched == null)
{
return null;
}
var keys = ParcelBillingCalculator.BuildPriceZoneKeys(matched, area); var keys = ParcelBillingCalculator.BuildPriceZoneKeys(matched, area);
return keys.Count > 0 ? keys : null; return keys.Count > 0 ? keys : null;
} }

View File

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

View File

@ -73,6 +73,57 @@ public class PriceCheckSnapshotVerificationTests
ParcelBillingCalculator.WeightInLeftOpenRightClosedInterval(0M, start, end).Should().BeFalse(); 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] [Fact]
public void UserSnapshot_Zone3_Weight05_ShouldMatchFreight11AndHandling20() public void UserSnapshot_Zone3_Weight05_ShouldMatchFreight11AndHandling20()
{ {