diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelQuote.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelQuote.cs
index eab16a7..f5fffad 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelQuote.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelQuote.cs
@@ -672,21 +672,68 @@ public partial class OrderService
await TryAddAsync(null, null, false);
- // 如果按「仅目的国」都无法匹配到任何价格分区,则不要再用卖价快照的全量分区键兜底。
- // 否则会导致未配置国家也能返回参考价格(estimated),与预期不符。
- if (attempts.Count == 0)
+ // 精确命中(如无邮编区间的国家级分区)后,仍可用快照内其它分区键作参考兜底
+ if (attempts.Count > 0)
{
+ foreach (var zoneKey in ParcelBillingCalculator.CollectDistinctDetailZoneKeys(snapshotBinding.Price))
+ {
+ if (attempts.Any(x => x.ZoneKeys.Any(k => string.Equals(k, zoneKey, StringComparison.OrdinalIgnoreCase))))
+ {
+ continue;
+ }
+
+ attempts.Add(new QuoteZoneAttempt([zoneKey], true, zoneKey));
+ }
+
return attempts;
}
- foreach (var zoneKey in ParcelBillingCalculator.CollectDistinctDetailZoneKeys(snapshotBinding.Price))
+ // 仅国家+重量:分区规则带邮编时无法精确命中,按「同国家」分区给出估算价;
+ // 快照中完全没有该国家时不跨国家兜底。
+ if (snapshotBinding.Price?.PriceAreaInfos is { Count: > 0 })
{
- if (attempts.Any(x => x.ZoneKeys.Any(k => string.Equals(k, zoneKey, StringComparison.OrdinalIgnoreCase))))
+ foreach (var zoneKey in ParcelBillingCalculator.CollectCountryEstimateZoneKeys(
+ snapshotBinding.Price,
+ context.CountryCode))
{
- continue;
+ attempts.Add(new QuoteZoneAttempt([zoneKey], true, zoneKey));
}
- attempts.Add(new QuoteZoneAttempt([zoneKey], true, zoneKey));
+ return attempts;
+ }
+
+ // 无快照分区明细时,退回库表:同国家且不要求邮编的规则
+ var priceAreaId = ResolveEffectivePriceAreaId(product, snapshotBinding.Rules, snapshotBinding.Price);
+ if (priceAreaId is > 0)
+ {
+ var area = await KpiAreaRepository.FirstOrDefaultAsync(x => x.Id == priceAreaId.Value);
+ var rules = await KpiAreaRepository.GetAllInfosByAreaIdAsync(priceAreaId.Value);
+ foreach (var rule in rules)
+ {
+ if (!string.IsNullOrWhiteSpace(rule.CountryCode)
+ && !string.Equals(rule.CountryCode, context.CountryCode, StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (!string.IsNullOrWhiteSpace(rule.Province) || !string.IsNullOrWhiteSpace(rule.City))
+ {
+ continue;
+ }
+
+ var keys = ParcelBillingCalculator.BuildPriceZoneKeys(rule, area);
+ if (keys.Count == 0)
+ {
+ continue;
+ }
+
+ if (attempts.Any(x => x.ZoneKeys.SequenceEqual(keys, StringComparer.OrdinalIgnoreCase)))
+ {
+ continue;
+ }
+
+ attempts.Add(new QuoteZoneAttempt(keys.ToList(), true, keys[0]));
+ }
}
return attempts;
diff --git a/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs b/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
index 9e9d1b8..c7a010d 100644
--- a/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
+++ b/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
@@ -156,6 +156,32 @@ internal static class ParcelBillingCalculator
return keys.Count > 0 ? keys : null;
}
+ ///
+ /// 查价仅填国家时:若快照已配置该目的国分区(即使规则带邮编区间),
+ /// 则返回卖价明细中的分区键供估算报价;未配置该国家时返回空(不跨国家兜底)。
+ ///
+ public static IReadOnlyList CollectCountryEstimateZoneKeys(
+ SalesProductPricePublishSnapshotDto? priceSnapshot,
+ string countryCode)
+ {
+ if (priceSnapshot?.PriceAreaInfos == null
+ || priceSnapshot.PriceAreaInfos.Count == 0
+ || string.IsNullOrWhiteSpace(countryCode))
+ {
+ return [];
+ }
+
+ var countryConfigured = priceSnapshot.PriceAreaInfos.Any(rule =>
+ string.IsNullOrWhiteSpace(rule.CountryCode)
+ || string.Equals(rule.CountryCode, countryCode, StringComparison.OrdinalIgnoreCase));
+ if (!countryConfigured)
+ {
+ return [];
+ }
+
+ return CollectDistinctDetailZoneKeys(priceSnapshot);
+ }
+
public static string? ResolveZoneName(KpiAreaInfo? matched, KpiArea? area)
{
if (matched == null)
diff --git a/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs b/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
index 5b5ea56..0dd3f7d 100644
--- a/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
+++ b/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
@@ -303,4 +303,45 @@ public class PriceCheckSnapshotVerificationTests
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();
+ }
}