diff --git a/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs b/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
index d4d6fdc..809b136 100644
--- a/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
+++ b/src/Oms.Application/Dtos/Orders/ProductBillingSnapshotDtos.cs
@@ -23,14 +23,14 @@ public class SalesProductPricePublishSnapshotDto
public string? PriceAreaName { get; set; }
///
- /// 价格分区明细(国家/邮编等),发布时从 t_kpi_area_info 固化。
+ /// 价格分区明细(国家/邮编等),发布时从 t_kpi_area_info 固化;邮编为左开右闭 (left,right]。
///
- public List PriceAreaInfos { get; set; } = [];
+ public List PriceAreaInfos { get; set; } = [];
///
- /// 价格项列表。
+ /// 价格项列表(不含创建/更新时间)。
///
- public List Prices { get; set; } = [];
+ public List Prices { get; set; } = [];
}
///
@@ -59,9 +59,9 @@ public class SalesProductRulesPublishSnapshotDto
public string? BlacklistAreaName { get; set; }
///
- /// 黑名单分区明细,发布时从 t_kpi_area_info 固化。
+ /// 黑名单分区明细,发布时从 t_kpi_area_info 固化;邮编为左开右闭 (left,right]。
///
- public List BlacklistAreaInfos { get; set; } = [];
+ public List BlacklistAreaInfos { get; set; } = [];
///
/// 偏远分区 ID(发布时固化)。
@@ -74,9 +74,9 @@ public class SalesProductRulesPublishSnapshotDto
public string? RemoteAreaName { get; set; }
///
- /// 偏远分区明细,发布时从 t_kpi_area_info 固化。
+ /// 偏远分区明细,发布时从 t_kpi_area_info 固化;邮编为左开右闭 (left,right]。
///
- public List RemoteAreaInfos { get; set; } = [];
+ public List RemoteAreaInfos { get; set; } = [];
///
/// 计重规则 ID(发布时固化)。
@@ -91,7 +91,7 @@ public class SalesProductRulesPublishSnapshotDto
///
/// 计重规则明细,发布时从 t_weight_rule_info 固化。
///
- public List WeightRuleInfos { get; set; } = [];
+ public List WeightRuleInfos { get; set; } = [];
///
/// 计重进位 ID(发布时固化)。
@@ -106,7 +106,7 @@ public class SalesProductRulesPublishSnapshotDto
///
/// 计重进位区间,发布时从 t_weight_rounding_info 固化。
///
- public List WeightRoundingInfos { get; set; } = [];
+ public List WeightRoundingInfos { get; set; } = [];
}
///
diff --git a/src/Oms.Application/Dtos/Orders/PublishSnapshotRuleDtos.cs b/src/Oms.Application/Dtos/Orders/PublishSnapshotRuleDtos.cs
new file mode 100644
index 0000000..9321cfe
--- /dev/null
+++ b/src/Oms.Application/Dtos/Orders/PublishSnapshotRuleDtos.cs
@@ -0,0 +1,86 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Oms.Application.Dtos.Channels;
+
+namespace Oms.Application.Dtos.Orders;
+
+///
+/// KPI 分区快照行(黑名单/偏远/价格分区),仅保留计费匹配字段。
+///
+public class KpiAreaSnapshotRuleDto
+{
+ public int Id { get; set; }
+
+ public string? CountryCode { get; set; }
+
+ public string? Province { get; set; }
+
+ public string? City { get; set; }
+
+ ///
+ /// 邮编区间,左开右闭,如 (50000,90000]。
+ ///
+ public string? PostcodeRange { get; set; }
+
+ /// 旧快照兼容:反序列化后由 转为 PostcodeRange 并清空。
+ public string? LeftPostcode { get; set; }
+
+ /// 旧快照兼容:反序列化后由 转为 PostcodeRange 并清空。
+ public string? RightPostcode { get; set; }
+}
+
+///
+/// 计重规则快照行。
+///
+public class WeightRuleSnapshotRuleDto
+{
+ public int Id { get; set; }
+
+ public string Condition { get; set; } = string.Empty;
+
+ public string BillableWeight { get; set; } = string.Empty;
+
+ public string VolumetricFactor { get; set; } = string.Empty;
+}
+
+///
+/// 计重进位快照行。
+///
+public class WeightRoundingSnapshotRangeDto
+{
+ public int Id { get; set; }
+
+ public decimal Start { get; set; }
+
+ public decimal End { get; set; }
+
+ public string? Public { get; set; }
+}
+
+///
+/// 卖价项快照(不含创建/更新时间)。
+///
+public class SalesProductPriceSnapshotItemDto
+{
+ public int Id { get; set; }
+
+ public int SerialNumber { get; set; }
+
+ public byte Status { get; set; }
+
+ public string PriceName { get; set; } = string.Empty;
+
+ public string CostItem { get; set; } = string.Empty;
+
+ public string CostUnit { get; set; } = string.Empty;
+
+ public string Currency { get; set; } = string.Empty;
+
+ public string? ChargingTime { get; set; }
+
+ public string? ChargingCondition { get; set; }
+
+ public List Factors { get; set; } = [];
+
+ public List Details { get; set; } = [];
+}
diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
index 29d8f24..9306171 100644
--- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
+++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs
@@ -184,7 +184,7 @@ public partial class SalesProductService
var priceSnapshot = new SalesProductPricePublishSnapshot
{
SalesProductId = salesProductId,
- Prices = await GetPriceOutputsAsync(salesProductId)
+ Prices = (await GetPriceOutputsAsync(salesProductId)).Select(PublishSnapshotSlimmer.SlimPrice).ToList()
};
await PriceAreaSnapshotHelper.ApplyPriceAreaToSnapshotAsync(priceSnapshot, product, KpiAreaRepository);
return priceSnapshot;
@@ -230,7 +230,7 @@ public partial class SalesProductService
PublishUser = string.IsNullOrWhiteSpace(CurrentUser.UserName) ? CurrentUser.GetId().ToString() : CurrentUser.UserName!,
PublishTime = DateTime.Now,
Description = description,
- Content = OmsDocHelper.SerializeJson(snapshot)
+ Content = OmsDocHelper.SerializeSnapshotJson(snapshot)
});
}
@@ -249,7 +249,7 @@ public partial class SalesProductService
if (operatorType == PriceOperatorType)
{
- var snapshot = OmsDocHelper.DeserializeJson(record.Content);
+ var snapshot = OmsDocHelper.DeserializePriceSnapshot(record.Content);
if (snapshot == null)
{
throw new Fake.Application.BusinessException("发布快照无效");
@@ -259,12 +259,12 @@ public partial class SalesProductService
await SavePriceAsync(new SalesProductPriceSaveAllInput
{
SalesProductId = input.SalesProductId,
- Prices = snapshot.Prices.Select(MapPriceSaveInput).ToList()
+ Prices = snapshot.Prices.Select(price => MapPriceSaveInput(price, input.SalesProductId)).ToList()
});
return;
}
- var rulesSnapshot = OmsDocHelper.DeserializeJson(record.Content);
+ var rulesSnapshot = OmsDocHelper.DeserializeRulesSnapshot(record.Content);
if (rulesSnapshot == null)
{
throw new Fake.Application.BusinessException("发布快照无效");
@@ -408,12 +408,47 @@ public partial class SalesProductService
}).ToList();
}
- private static SalesProductPriceSaveInput MapPriceSaveInput(SalesProductPriceListOutput output)
+ private static SalesProductPriceSaveInput MapPriceSaveInput(SalesProductPriceListOutput output) =>
+ MapPriceSaveInput(output, output.SalesProductId);
+
+ private static SalesProductPriceSaveInput MapPriceSaveInput(SalesProductPriceSnapshotItemDto output, int salesProductId)
{
return new SalesProductPriceSaveInput
{
Id = output.Id,
- SalesProductId = output.SalesProductId,
+ SalesProductId = salesProductId,
+ SerialNumber = output.SerialNumber,
+ Status = (EnableStatus)output.Status,
+ PriceName = output.PriceName,
+ CostItem = output.CostItem,
+ CostUnit = output.CostUnit,
+ Currency = output.Currency,
+ ChargingTime = output.ChargingTime,
+ ChargingCondition = output.ChargingCondition,
+ Factors = output.Factors.Select(item => new ChannelPriceFactorInput
+ {
+ Id = item.Id,
+ SerialNumber = item.SerialNumber,
+ Factor = item.Factor,
+ FactorValues = item.FactorValues
+ }).ToList(),
+ Details = output.Details.Select(item => new ChannelPriceDetailInput
+ {
+ Id = item.Id,
+ FactorValuesJson = item.FactorValuesJson,
+ CalculationFormula = item.CalculationFormula,
+ CalculatedValue = item.CalculatedValue,
+ CustomerGroupValues = item.CustomerGroupValues
+ }).ToList()
+ };
+ }
+
+ private static SalesProductPriceSaveInput MapPriceSaveInput(SalesProductPriceListOutput output, int salesProductId)
+ {
+ return new SalesProductPriceSaveInput
+ {
+ Id = output.Id,
+ SalesProductId = salesProductId,
SerialNumber = output.SerialNumber,
Status = (EnableStatus)output.Status,
PriceName = output.PriceName,
diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
index 72998d8..7b21faa 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
@@ -250,7 +250,7 @@ public partial class OrderService
}
else
{
- rulesSnapshot = OmsDocHelper.DeserializeJson(rulesRecord.Content)
+ rulesSnapshot = OmsDocHelper.DeserializeRulesSnapshot(rulesRecord.Content)
?? throw new BusinessException("未命中规则快照版本(内存/Redis/DB)");
}
@@ -265,7 +265,7 @@ public partial class OrderService
}
else
{
- priceSnapshot = OmsDocHelper.DeserializeJson(priceRecord.Content)
+ priceSnapshot = OmsDocHelper.DeserializePriceSnapshot(priceRecord.Content)
?? throw new BusinessException("未命中价格快照版本(内存/Redis/DB)");
if (priceSnapshot.Prices.Count == 0)
{
@@ -330,7 +330,7 @@ public partial class OrderService
var snapshot = new SalesProductPricePublishSnapshotDto
{
SalesProductId = product.Id,
- Prices = prices.Select(price => new SalesProductPriceListOutput
+ Prices = prices.Select(price => PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = price.Id,
SalesProductId = price.SalesProductId,
@@ -348,7 +348,7 @@ public partial class OrderService
Details = detailMap.TryGetValue(price.Id, out var details)
? details.Select(MapPriceDetailOutput).ToList()
: []
- }).ToList()
+ })).ToList()
};
await PriceAreaSnapshotHelper.ApplyPriceAreaToSnapshotAsync(snapshot, product, KpiAreaRepository);
return snapshot;
diff --git a/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs b/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
index 4f24e7c..29ca860 100644
--- a/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
+++ b/src/Oms.Application/Services/Orders/ParcelBillingCalculator.cs
@@ -146,7 +146,7 @@ internal static class ParcelBillingCalculator
}
var matched = priceSnapshot.PriceAreaInfos.FirstOrDefault(rule =>
- IsAreaMatched(rule, countryCode, province, city, zipCode));
+ KpiAreaSnapshotMapper.IsRuleMatched(rule, countryCode, province, city, zipCode));
if (matched == null)
{
return null;
@@ -221,6 +221,62 @@ internal static class ParcelBillingCalculator
return keys.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
}
+ public static IReadOnlyList BuildPriceZoneKeys(
+ KpiAreaSnapshotRuleDto matched,
+ string? areaName,
+ int? areaId)
+ {
+ var keys = new List();
+ var name = ResolveZoneName(matched, areaName);
+ if (!string.IsNullOrWhiteSpace(name))
+ {
+ keys.Add(name);
+ }
+
+ keys.Add(matched.Id.ToString(CultureInfo.InvariantCulture));
+ if (!string.IsNullOrWhiteSpace(matched.Province))
+ {
+ keys.Add(matched.Province);
+ }
+
+ if (!string.IsNullOrWhiteSpace(matched.CountryCode))
+ {
+ keys.Add(matched.CountryCode);
+ }
+
+ if (areaId is > 0)
+ {
+ keys.Add(areaId.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ if (!string.IsNullOrWhiteSpace(areaName))
+ {
+ keys.Add(areaName);
+ }
+
+ return keys.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+ }
+
+ public static string? ResolveZoneName(KpiAreaSnapshotRuleDto matched, string? areaName)
+ {
+ if (!string.IsNullOrWhiteSpace(matched.Province))
+ {
+ return matched.Province;
+ }
+
+ if (!string.IsNullOrWhiteSpace(matched.City))
+ {
+ return matched.City;
+ }
+
+ if (!string.IsNullOrWhiteSpace(matched.CountryCode))
+ {
+ return matched.CountryCode;
+ }
+
+ return areaName;
+ }
+
public static IReadOnlyList BuildPriceZoneKeys(
KpiAreaInfoOutput matched,
string? areaName,
diff --git a/src/Oms.Application/Shared/KpiAreaSnapshotMapper.cs b/src/Oms.Application/Shared/KpiAreaSnapshotMapper.cs
new file mode 100644
index 0000000..166cf03
--- /dev/null
+++ b/src/Oms.Application/Shared/KpiAreaSnapshotMapper.cs
@@ -0,0 +1,105 @@
+using System.Text.RegularExpressions;
+using Oms.Application.Dtos.Orders;
+using Oms.Domain.ChannelsAggregate.Entities;
+
+namespace Oms.Application.Shared;
+
+///
+/// KPI 分区快照映射与左开右闭邮编匹配。
+///
+public static class KpiAreaSnapshotMapper
+{
+ private static readonly Regex PostcodeRangePattern = new(
+ @"^\(\s*([^,)\]]*)\s*,\s*([^,)\]]*)\s*\]$",
+ RegexOptions.Compiled | RegexOptions.CultureInvariant);
+
+ public static KpiAreaSnapshotRuleDto MapToSnapshotRule(KpiAreaInfo entity) =>
+ new()
+ {
+ Id = entity.Id,
+ CountryCode = entity.CountryCode,
+ Province = entity.Province,
+ City = entity.City,
+ PostcodeRange = BuildPostcodeRange(entity.LeftPostcode, entity.RightPostcode)
+ };
+
+ public static string? BuildPostcodeRange(string? left, string? right)
+ {
+ if (string.IsNullOrWhiteSpace(left) && string.IsNullOrWhiteSpace(right))
+ {
+ return null;
+ }
+
+ return $"({left?.Trim()},{right?.Trim()}]";
+ }
+
+ public static string? ResolvePostcodeRange(KpiAreaSnapshotRuleDto rule)
+ {
+ if (!string.IsNullOrWhiteSpace(rule.PostcodeRange))
+ {
+ return rule.PostcodeRange.Trim();
+ }
+
+ return BuildPostcodeRange(rule.LeftPostcode, rule.RightPostcode);
+ }
+
+ public static bool IsRuleMatched(
+ KpiAreaSnapshotRuleDto rule,
+ string countryCode,
+ string? province,
+ string? city,
+ string? zipCode)
+ {
+ if (!string.IsNullOrWhiteSpace(rule.CountryCode)
+ && !string.Equals(rule.CountryCode, countryCode, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (!string.IsNullOrWhiteSpace(rule.Province)
+ && !string.Equals(rule.Province, province, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ if (!string.IsNullOrWhiteSpace(rule.City)
+ && !string.Equals(rule.City, city, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+
+ var postcodeRange = ResolvePostcodeRange(rule);
+ if (string.IsNullOrWhiteSpace(postcodeRange))
+ {
+ return true;
+ }
+
+ if (string.IsNullOrWhiteSpace(zipCode))
+ {
+ return false;
+ }
+
+ return IsZipInLeftOpenRightClosedRange(zipCode.Trim(), postcodeRange);
+ }
+
+ public static bool IsZipInLeftOpenRightClosedRange(string zipCode, string postcodeRange)
+ {
+ var match = PostcodeRangePattern.Match(postcodeRange.Trim());
+ if (!match.Success)
+ {
+ return false;
+ }
+
+ var left = match.Groups[1].Value.Trim();
+ var right = match.Groups[2].Value.Trim();
+ if (long.TryParse(zipCode, out var zip)
+ && long.TryParse(left, out var leftValue)
+ && long.TryParse(right, out var rightValue))
+ {
+ return zip > leftValue && zip <= rightValue;
+ }
+
+ return string.Compare(zipCode, left, StringComparison.OrdinalIgnoreCase) > 0
+ && string.Compare(zipCode, right, StringComparison.OrdinalIgnoreCase) <= 0;
+ }
+}
diff --git a/src/Oms.Application/Shared/OmsDocHelper.cs b/src/Oms.Application/Shared/OmsDocHelper.cs
index 3299407..102b40d 100644
--- a/src/Oms.Application/Shared/OmsDocHelper.cs
+++ b/src/Oms.Application/Shared/OmsDocHelper.cs
@@ -1,5 +1,7 @@
using System.Text.Json;
using System.Text.Json.Nodes;
+using System.Text.Json.Serialization;
+using Oms.Application.Dtos.Orders;
using Oms.Domain.OrdersAggregate.Consts;
using Oms.Domain.SystemAggregate.Consts;
@@ -15,6 +17,12 @@ public static class OmsDocHelper
PropertyNameCaseInsensitive = true
};
+ private static readonly JsonSerializerOptions SnapshotJsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ };
+
///
/// 将页码规范为至少 1。
///
@@ -93,6 +101,33 @@ public static class OmsDocHelper
return JsonSerializer.Serialize(value, JsonOptions);
}
+ ///
+ /// 序列化发布快照(忽略 null,避免写出旧兼容字段)。
+ ///
+ public static string? SerializeSnapshotJson(T? value)
+ {
+ if (value is null)
+ {
+ return null;
+ }
+
+ return JsonSerializer.Serialize(value, SnapshotJsonOptions);
+ }
+
+ public static SalesProductRulesPublishSnapshotDto? DeserializeRulesSnapshot(string? json)
+ {
+ var snapshot = DeserializeJson(json);
+ PublishSnapshotNormalizer.NormalizeRulesSnapshot(snapshot);
+ return snapshot;
+ }
+
+ public static SalesProductPricePublishSnapshotDto? DeserializePriceSnapshot(string? json)
+ {
+ var snapshot = DeserializeJson(json);
+ PublishSnapshotNormalizer.NormalizePriceSnapshot(snapshot);
+ return snapshot;
+ }
+
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
{
JsonValueKind.Object => element.EnumerateObject()
diff --git a/src/Oms.Application/Shared/PriceAreaSnapshotHelper.cs b/src/Oms.Application/Shared/PriceAreaSnapshotHelper.cs
index ab0bd80..6a4b7e2 100644
--- a/src/Oms.Application/Shared/PriceAreaSnapshotHelper.cs
+++ b/src/Oms.Application/Shared/PriceAreaSnapshotHelper.cs
@@ -37,7 +37,7 @@ public static class PriceAreaSnapshotHelper
var area = await kpiAreaRepository.FirstOrDefaultAsync(x => x.Id == priceAreaId.Value, cancellationToken);
snapshot.PriceAreaName = area?.Name;
var infos = await kpiAreaRepository.GetAllInfosByAreaIdAsync(priceAreaId.Value, cancellationToken);
- snapshot.PriceAreaInfos = infos.Select(MapInfoOutput).ToList();
+ snapshot.PriceAreaInfos = infos.Select(KpiAreaSnapshotMapper.MapToSnapshotRule).ToList();
}
internal static async Task ResolvePriceAreaIdAsync(
@@ -74,19 +74,4 @@ public static class PriceAreaSnapshotHelper
return null;
}
-
- private static KpiAreaInfoOutput MapInfoOutput(KpiAreaInfo entity) =>
- new()
- {
- Id = entity.Id,
- AreaId = entity.AreaId,
- CountryCode = entity.CountryCode,
- CountryName = entity.CountryName,
- Province = entity.Province,
- City = entity.City,
- LeftPostcode = entity.LeftPostcode,
- RightPostcode = entity.RightPostcode,
- CreateTime = entity.CreateTime,
- UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
- };
}
diff --git a/src/Oms.Application/Shared/PublishSnapshotNormalizer.cs b/src/Oms.Application/Shared/PublishSnapshotNormalizer.cs
new file mode 100644
index 0000000..ac3ff63
--- /dev/null
+++ b/src/Oms.Application/Shared/PublishSnapshotNormalizer.cs
@@ -0,0 +1,50 @@
+using Oms.Application.Dtos.Orders;
+
+namespace Oms.Application.Shared;
+
+///
+/// 发布快照读取后规范化(旧字段迁移、去掉冗余)。
+///
+public static class PublishSnapshotNormalizer
+{
+ public static void NormalizeRulesSnapshot(SalesProductRulesPublishSnapshotDto? snapshot)
+ {
+ if (snapshot is null)
+ {
+ return;
+ }
+
+ NormalizeKpiAreas(snapshot.BlacklistAreaInfos);
+ NormalizeKpiAreas(snapshot.RemoteAreaInfos);
+ }
+
+ public static void NormalizePriceSnapshot(SalesProductPricePublishSnapshotDto? snapshot)
+ {
+ if (snapshot is null)
+ {
+ return;
+ }
+
+ NormalizeKpiAreas(snapshot.PriceAreaInfos);
+ }
+
+ public static void NormalizeKpiAreas(List? areas)
+ {
+ if (areas is null)
+ {
+ return;
+ }
+
+ foreach (var area in areas)
+ {
+ if (string.IsNullOrWhiteSpace(area.PostcodeRange)
+ && (!string.IsNullOrWhiteSpace(area.LeftPostcode) || !string.IsNullOrWhiteSpace(area.RightPostcode)))
+ {
+ area.PostcodeRange = KpiAreaSnapshotMapper.BuildPostcodeRange(area.LeftPostcode, area.RightPostcode);
+ }
+
+ area.LeftPostcode = null;
+ area.RightPostcode = null;
+ }
+ }
+}
diff --git a/src/Oms.Application/Shared/PublishSnapshotSlimmer.cs b/src/Oms.Application/Shared/PublishSnapshotSlimmer.cs
new file mode 100644
index 0000000..99e01fe
--- /dev/null
+++ b/src/Oms.Application/Shared/PublishSnapshotSlimmer.cs
@@ -0,0 +1,63 @@
+using Oms.Application.Dtos.Channels;
+using Oms.Application.Dtos.Orders;
+using Oms.Domain.ChannelsAggregate.Entities;
+
+namespace Oms.Application.Shared;
+
+///
+/// 发布快照瘦身:去掉审计时间等计费无用字段。
+///
+public static class PublishSnapshotSlimmer
+{
+ public static SalesProductPriceSnapshotItemDto SlimPrice(SalesProductPriceListOutput price) =>
+ new()
+ {
+ Id = price.Id,
+ SerialNumber = price.SerialNumber,
+ Status = price.Status,
+ PriceName = price.PriceName,
+ CostItem = price.CostItem,
+ CostUnit = price.CostUnit,
+ Currency = price.Currency,
+ ChargingTime = price.ChargingTime,
+ ChargingCondition = price.ChargingCondition,
+ Factors = price.Factors,
+ Details = price.Details
+ };
+
+ public static WeightRuleSnapshotRuleDto SlimWeightRule(WeightRuleInfo entity) =>
+ new()
+ {
+ Id = entity.Id,
+ Condition = entity.Condition,
+ BillableWeight = entity.BillableWeight,
+ VolumetricFactor = entity.VolumetricFactor
+ };
+
+ public static WeightRoundingSnapshotRangeDto SlimWeightRounding(WeightRoundingInfo entity) =>
+ new()
+ {
+ Id = entity.Id,
+ Start = entity.StartValue,
+ End = entity.EndValue,
+ Public = entity.Public
+ };
+
+ public static WeightRuleInfo ToWeightRuleEntity(WeightRuleSnapshotRuleDto rule) =>
+ new()
+ {
+ Id = rule.Id,
+ Condition = rule.Condition,
+ BillableWeight = rule.BillableWeight,
+ VolumetricFactor = rule.VolumetricFactor
+ };
+
+ public static WeightRoundingInfo ToWeightRoundingEntity(WeightRoundingSnapshotRangeDto range) =>
+ new()
+ {
+ Id = range.Id,
+ StartValue = range.Start,
+ EndValue = range.End,
+ Public = range.Public
+ };
+}
diff --git a/src/Oms.Application/Shared/RulesSnapshotHelper.cs b/src/Oms.Application/Shared/RulesSnapshotHelper.cs
index 6ea474a..fc5744c 100644
--- a/src/Oms.Application/Shared/RulesSnapshotHelper.cs
+++ b/src/Oms.Application/Shared/RulesSnapshotHelper.cs
@@ -1,4 +1,3 @@
-using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders;
using Oms.Application.Services.Orders;
using Oms.Domain.ChannelsAggregate.Entities;
@@ -54,7 +53,7 @@ public static class RulesSnapshotHelper
/// 从快照内嵌黑名单/偏远明细匹配地址(不查库)。
///
public static bool IsAreaHitFromSnapshot(
- IReadOnlyList? infos,
+ IReadOnlyList? infos,
string countryCode,
string? province,
string? city,
@@ -65,7 +64,7 @@ public static class RulesSnapshotHelper
return false;
}
- return infos.Any(rule => ParcelBillingCalculator.IsAreaMatched(
+ return infos.Any(rule => KpiAreaSnapshotMapper.IsRuleMatched(
rule,
countryCode,
province,
@@ -76,20 +75,20 @@ public static class RulesSnapshotHelper
///
/// 将快照内嵌计重明细转为实体供计算器使用。
///
- public static List ToWeightRuleEntities(IReadOnlyList infos) =>
- infos.Select(MapWeightRuleInfo).ToList();
+ public static List ToWeightRuleEntities(IReadOnlyList infos) =>
+ infos.Select(PublishSnapshotSlimmer.ToWeightRuleEntity).ToList();
///
/// 将快照内嵌进位区间转为实体供计算器使用。
///
- public static List ToWeightRoundingEntities(IReadOnlyList infos) =>
- infos.Select(MapWeightRoundingInfo).ToList();
+ public static List ToWeightRoundingEntities(IReadOnlyList infos) =>
+ infos.Select(PublishSnapshotSlimmer.ToWeightRoundingEntity).ToList();
private static async Task ApplyKpiAreaToSnapshotAsync(
SalesProductRulesPublishSnapshotDto snapshot,
int? areaId,
IKpiAreaRepository kpiAreaRepository,
- Action> assign,
+ Action> assign,
CancellationToken cancellationToken)
{
if (!areaId.HasValue || areaId.Value <= 0)
@@ -104,7 +103,7 @@ public static class RulesSnapshotHelper
snapshot,
areaId,
area?.Name,
- infos.Select(MapKpiAreaInfo).ToList());
+ infos.Select(KpiAreaSnapshotMapper.MapToSnapshotRule).ToList());
}
private static async Task ApplyWeightRuleToSnapshotAsync(
@@ -125,7 +124,7 @@ public static class RulesSnapshotHelper
var rule = await weightRuleRepository.FirstOrDefaultAsync(x => x.Id == product.WeightRuleId.Value, cancellationToken);
snapshot.WeightRuleName = rule?.Name;
var infos = await weightRuleRepository.GetAllInfosByRuleIdAsync(product.WeightRuleId.Value, cancellationToken);
- snapshot.WeightRuleInfos = infos.Select(MapWeightRuleInfoOutput).ToList();
+ snapshot.WeightRuleInfos = infos.Select(PublishSnapshotSlimmer.SlimWeightRule).ToList();
}
private static async Task ApplyWeightRoundingToSnapshotAsync(
@@ -150,71 +149,6 @@ public static class RulesSnapshotHelper
var infos = await weightRoundingRepository.GetAllInfosByRoundingIdAsync(
product.WeightRoundingId.Value,
cancellationToken);
- snapshot.WeightRoundingInfos = infos.Select(MapWeightRoundingInfoOutput).ToList();
+ snapshot.WeightRoundingInfos = infos.Select(PublishSnapshotSlimmer.SlimWeightRounding).ToList();
}
-
- private static KpiAreaInfoOutput MapKpiAreaInfo(KpiAreaInfo entity) =>
- new()
- {
- Id = entity.Id,
- AreaId = entity.AreaId,
- CountryCode = entity.CountryCode,
- CountryName = entity.CountryName,
- Province = entity.Province,
- City = entity.City,
- LeftPostcode = entity.LeftPostcode,
- RightPostcode = entity.RightPostcode,
- CreateTime = entity.CreateTime,
- UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
- };
-
- private static WeightRuleInfoOutput MapWeightRuleInfoOutput(WeightRuleInfo entity) =>
- new()
- {
- Id = entity.Id,
- RuleId = entity.RuleId,
- Client = entity.Client,
- Condition = entity.Condition,
- BillableWeight = entity.BillableWeight,
- VolumetricFactor = entity.VolumetricFactor,
- CreateTime = entity.CreateTime,
- UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
- };
-
- private static WeightRoundingInfoOutput MapWeightRoundingInfoOutput(WeightRoundingInfo entity) =>
- new()
- {
- Id = entity.Id,
- RoundingId = entity.RoundingId,
- StartValue = entity.StartValue,
- EndValue = entity.EndValue,
- Public = entity.Public,
- CustomerGroup1 = entity.CustomerGroup1,
- CustomerGroup2 = entity.CustomerGroup2,
- CreateTime = entity.CreateTime,
- UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
- };
-
- private static WeightRuleInfo MapWeightRuleInfo(WeightRuleInfoOutput output) =>
- new()
- {
- Id = output.Id,
- RuleId = output.RuleId,
- Client = output.Client,
- Condition = output.Condition,
- BillableWeight = output.BillableWeight,
- VolumetricFactor = output.VolumetricFactor
- };
-
- private static WeightRoundingInfo MapWeightRoundingInfo(WeightRoundingInfoOutput output) =>
- new()
- {
- Id = output.Id,
- RoundingId = output.RoundingId,
- StartValue = output.StartValue,
- EndValue = output.EndValue,
- Public = output.Public,
- CustomerGroup1 = output.CustomerGroup1,
- CustomerGroup2 = output.CustomerGroup2
- };
}
diff --git a/src/Oms.Tests/KpiAreaSnapshotMapperTests.cs b/src/Oms.Tests/KpiAreaSnapshotMapperTests.cs
new file mode 100644
index 0000000..a612862
--- /dev/null
+++ b/src/Oms.Tests/KpiAreaSnapshotMapperTests.cs
@@ -0,0 +1,23 @@
+using FluentAssertions;
+using Oms.Application.Shared;
+
+namespace Oms.Tests;
+
+public class KpiAreaSnapshotMapperTests
+{
+ [Theory]
+ [InlineData("06000", "(00000,50000]", true)]
+ [InlineData("00000", "(00000,50000]", false)]
+ [InlineData("50000", "(00000,50000]", true)]
+ [InlineData("50001", "(00000,50000]", false)]
+ public void IsZipInLeftOpenRightClosedRange_ShouldMatchExpected(string zip, string range, bool expected)
+ {
+ KpiAreaSnapshotMapper.IsZipInLeftOpenRightClosedRange(zip, range).Should().Be(expected);
+ }
+
+ [Fact]
+ public void BuildPostcodeRange_ShouldFormatLeftOpenRightClosed()
+ {
+ KpiAreaSnapshotMapper.BuildPostcodeRange("00000", "50000").Should().Be("(00000,50000]");
+ }
+}
diff --git a/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs b/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
index 244976c..cdf68e6 100644
--- a/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
+++ b/src/Oms.Tests/PriceCheckSnapshotVerificationTests.cs
@@ -2,6 +2,7 @@ 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;
@@ -17,7 +18,7 @@ public class PriceCheckSnapshotVerificationTests
SalesProductId = 1,
Prices =
[
- new SalesProductPriceListOutput
+ PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 1,
Status = 1,
@@ -30,15 +31,15 @@ public class PriceCheckSnapshotVerificationTests
Detail(26, 1, "total", "3", "1,1.5", 13),
Detail(27, 1, "total", "3", "1.5,2", 14),
]
- },
- new SalesProductPriceListOutput
+ }),
+ PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 2,
Status = 1,
CostItem = "handling",
ChargingTime = "outbound",
Details = [Detail(28, 2, "total", "3", "0,10", 20)]
- }
+ })
]
};
@@ -90,7 +91,7 @@ public class PriceCheckSnapshotVerificationTests
SalesProductId = 1,
Prices =
[
- new SalesProductPriceListOutput
+ PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 1,
Status = 1,
@@ -100,14 +101,14 @@ public class PriceCheckSnapshotVerificationTests
Detail(34, 1, "total", "3", "(0-0.5]", 11),
Detail(35, 1, "total", "3", "(0.5-1]", 12),
]
- },
- new SalesProductPriceListOutput
+ }),
+ PublishSnapshotSlimmer.SlimPrice(new SalesProductPriceListOutput
{
Id = 2,
Status = 1,
CostItem = "handling",
Details = [Detail(38, 2, "total", "3", "(0-10]", 20)]
- }
+ })
]
};
@@ -151,23 +152,17 @@ public class PriceCheckSnapshotVerificationTests
PriceAreaName = "美国分区",
PriceAreaInfos =
[
- new KpiAreaInfoOutput
+ new KpiAreaSnapshotRuleDto
{
Id = 4,
- AreaId = 3,
CountryCode = "US",
- CountryName = "美国",
- LeftPostcode = "50000",
- RightPostcode = "90000"
+ PostcodeRange = "(50000,90000]"
},
- new KpiAreaInfoOutput
+ new KpiAreaSnapshotRuleDto
{
Id = 3,
- AreaId = 3,
CountryCode = "US",
- CountryName = "美国",
- LeftPostcode = "00000",
- RightPostcode = "50000"
+ PostcodeRange = "(00000,50000]"
}
]
};
diff --git a/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs b/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs
index 3ccdda0..c27ad84 100644
--- a/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs
+++ b/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs
@@ -79,10 +79,14 @@ public class RulesSnapshotHelperPublishTests
snapshot.WeightRoundingId.Should().Be(2);
snapshot.WeightRoundingInfos.Should().HaveCount(1);
- var json = OmsDocHelper.SerializeJson(snapshot);
- json.Should().Contain("BlacklistAreaInfos");
+ var json = OmsDocHelper.SerializeSnapshotJson(snapshot);
+ json.Should().Contain("PostcodeRange");
+ json.Should().Contain("(90000,90010]");
json.Should().Contain("WeightRuleInfos");
json.Should().Contain("WeightRoundingInfos");
json.Should().Contain("RemoteAreaInfos");
+ json.Should().NotContain("CreateTime");
+ json.Should().NotContain("UpdateTime");
+ json.Should().NotContain("LeftPostcode");
}
}
diff --git a/src/Oms.Tests/RulesSnapshotVerificationTests.cs b/src/Oms.Tests/RulesSnapshotVerificationTests.cs
index 5bc9206..8ec6e40 100644
--- a/src/Oms.Tests/RulesSnapshotVerificationTests.cs
+++ b/src/Oms.Tests/RulesSnapshotVerificationTests.cs
@@ -1,5 +1,4 @@
using FluentAssertions;
-using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders;
using Oms.Application.Services.Orders;
using Oms.Application.Shared;
@@ -18,34 +17,29 @@ public class RulesSnapshotVerificationTests
BlacklistAreaId = 10,
BlacklistAreaInfos =
[
- new KpiAreaInfoOutput
+ new KpiAreaSnapshotRuleDto
{
Id = 1,
- AreaId = 10,
CountryCode = "US",
- LeftPostcode = "90000",
- RightPostcode = "90010"
+ PostcodeRange = "(90000,90010]"
}
],
RemoteAreaId = 20,
RemoteAreaInfos =
[
- new KpiAreaInfoOutput
+ new KpiAreaSnapshotRuleDto
{
Id = 2,
- AreaId = 20,
CountryCode = "US",
- LeftPostcode = "06000",
- RightPostcode = "06999"
+ PostcodeRange = "(05999,06999]"
}
],
WeightRuleId = 1,
WeightRuleInfos =
[
- new WeightRuleInfoOutput
+ new WeightRuleSnapshotRuleDto
{
Id = 1,
- RuleId = 1,
VolumetricFactor = "6000",
Condition = "gw>vw",
BillableWeight = "gw"
@@ -54,12 +48,11 @@ public class RulesSnapshotVerificationTests
WeightRoundingId = 1,
WeightRoundingInfos =
[
- new WeightRoundingInfoOutput
+ new WeightRoundingSnapshotRangeDto
{
Id = 1,
- RoundingId = 1,
- StartValue = 0,
- EndValue = 30,
+ Start = 0,
+ End = 30,
Public = "0.5"
}
]
diff --git a/src/Oms.Tests/Support/ParcelBillingTestData.cs b/src/Oms.Tests/Support/ParcelBillingTestData.cs
index 57dffe2..dbb6595 100644
--- a/src/Oms.Tests/Support/ParcelBillingTestData.cs
+++ b/src/Oms.Tests/Support/ParcelBillingTestData.cs
@@ -1,6 +1,7 @@
using System.Globalization;
using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Orders;
+using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Entities;
namespace Oms.Tests.Support;
@@ -13,7 +14,11 @@ internal static class ParcelBillingTestData
public static SalesProductPricePublishSnapshotDto BuildReceivableSnapshot(
params SalesProductPriceListOutput[] prices) =>
- new() { SalesProductId = 1, Prices = prices.ToList() };
+ new()
+ {
+ SalesProductId = 1,
+ Prices = prices.Select(PublishSnapshotSlimmer.SlimPrice).ToList()
+ };
public static List BuildPayablePrices(
params ServiceChannelPriceListOutput[] prices) => prices.ToList();