From e2443ed6f891ad15c3accff696a27bd76427c195 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Tue, 28 Jul 2026 11:14:12 +0800 Subject: [PATCH] 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 --- .../Orders/OrderService.ParcelQuote.cs | 61 ++++++++++++++++--- .../Orders/ParcelBillingCalculator.cs | 26 ++++++++ .../PriceCheckSnapshotVerificationTests.cs | 41 +++++++++++++ 3 files changed, 121 insertions(+), 7 deletions(-) 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(); + } }