修复订单隔离和查价
This commit is contained in:
parent
00835bfadd
commit
b7ed14d6af
@ -84,13 +84,13 @@ public partial class OrderService
|
||||
SyncWeightRows(weights, forecast);
|
||||
await OrderRepository.ReplaceWeightsAsync(order.Id, weights);
|
||||
|
||||
var zoneName = await MatchPriceZoneAsync(
|
||||
var zoneKeys = await ResolvePriceZoneKeysAsync(
|
||||
product,
|
||||
country.CountryCode,
|
||||
input.Receiver?.Province,
|
||||
input.Receiver?.City,
|
||||
input.Receiver?.ZipCode);
|
||||
if (string.IsNullOrWhiteSpace(zoneName))
|
||||
if (zoneKeys == null || zoneKeys.Count == 0)
|
||||
{
|
||||
throw new BusinessException("价格分区未匹配(规则快照)");
|
||||
}
|
||||
@ -101,7 +101,7 @@ public partial class OrderService
|
||||
snapshotBinding.PriceVersion,
|
||||
snapshotBinding.PriceEffectiveTime,
|
||||
forecast.ChargeableWeight ?? 0,
|
||||
zoneName,
|
||||
zoneKeys,
|
||||
order.CargoAttribute ?? CargoAttributeCodes.General,
|
||||
customer?.CustomerGroup);
|
||||
|
||||
@ -398,7 +398,7 @@ public partial class OrderService
|
||||
zipCode));
|
||||
}
|
||||
|
||||
private async Task<string?> MatchPriceZoneAsync(
|
||||
private async Task<IReadOnlyList<string>?> ResolvePriceZoneKeysAsync(
|
||||
SalesProduct product,
|
||||
string countryCode,
|
||||
string? province,
|
||||
@ -407,10 +407,15 @@ public partial class OrderService
|
||||
{
|
||||
if (!product.PriceAreaId.HasValue || product.PriceAreaId <= 0)
|
||||
{
|
||||
return "默认分区";
|
||||
return new[] { "默认分区" };
|
||||
}
|
||||
|
||||
var area = await KpiAreaRepository.FirstOrDefaultAsync(x => x.Id == product.PriceAreaId.Value);
|
||||
if (area == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var rules = await KpiAreaRepository.GetAllInfosByAreaIdAsync(product.PriceAreaId.Value);
|
||||
var matched = rules.FirstOrDefault(rule => ParcelBillingCalculator.IsAreaMatched(
|
||||
rule,
|
||||
@ -418,7 +423,8 @@ public partial class OrderService
|
||||
province,
|
||||
city,
|
||||
zipCode));
|
||||
return ParcelBillingCalculator.ResolveZoneName(matched, area);
|
||||
var keys = ParcelBillingCalculator.BuildPriceZoneKeys(matched, area);
|
||||
return keys.Count > 0 ? keys : null;
|
||||
}
|
||||
|
||||
private async Task ApplyChargeableWeightAsync(SalesProduct product, OrderWeight forecast)
|
||||
|
||||
@ -148,27 +148,27 @@ public partial class OrderService
|
||||
var forecast = ParcelBillingCalculator.BuildForecastWeight(context);
|
||||
await ApplyChargeableWeightAsync(product, forecast);
|
||||
|
||||
var zoneName = await MatchPriceZoneAsync(
|
||||
var zoneKeys = await ResolvePriceZoneKeysAsync(
|
||||
product,
|
||||
context.CountryCode,
|
||||
context.Province,
|
||||
context.City,
|
||||
context.PostalCode);
|
||||
if (string.IsNullOrWhiteSpace(zoneName))
|
||||
if (zoneKeys == null || zoneKeys.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fees = ParcelBillingCalculator.CalculateForecastReceivableFees(
|
||||
var fees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
|
||||
snapshotBinding.Price,
|
||||
snapshotBinding.PriceVersion,
|
||||
snapshotBinding.PriceEffectiveTime,
|
||||
forecast.ChargeableWeight ?? 0,
|
||||
zoneName,
|
||||
zoneKeys,
|
||||
context.CargoAttribute,
|
||||
customerGroup);
|
||||
|
||||
if (fees.Count == 0 && ParcelBillingCalculator.HasEnabledForecastPriceItems(snapshotBinding.Price))
|
||||
if (fees.Count == 0 && ParcelBillingCalculator.HasEnabledQuotePriceItems(snapshotBinding.Price))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -189,7 +189,7 @@ public partial class OrderService
|
||||
PriceVersion = snapshotBinding.PriceVersion,
|
||||
VolumetricWeight = forecast.VolumetricWeight ?? 0,
|
||||
ChargeableWeight = forecast.ChargeableWeight ?? 0,
|
||||
ZoneName = zoneName,
|
||||
ZoneName = zoneKeys[0],
|
||||
IsRemote = isRemote,
|
||||
Fees = fees
|
||||
};
|
||||
|
||||
@ -147,12 +147,52 @@ internal static class ParcelBillingCalculator
|
||||
return area?.Name;
|
||||
}
|
||||
|
||||
public static List<OrderFee> CalculateForecastReceivableFees(
|
||||
public static IReadOnlyList<string> BuildPriceZoneKeys(KpiAreaInfo? matched, KpiArea? area)
|
||||
{
|
||||
var keys = new List<string>();
|
||||
if (matched != null)
|
||||
{
|
||||
var name = ResolveZoneName(matched, area);
|
||||
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.CountryName))
|
||||
{
|
||||
keys.Add(matched.CountryName);
|
||||
}
|
||||
}
|
||||
|
||||
if (area != null)
|
||||
{
|
||||
keys.Add(area.Id.ToString(CultureInfo.InvariantCulture));
|
||||
if (!string.IsNullOrWhiteSpace(area.Name))
|
||||
{
|
||||
keys.Add(area.Name);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(area.SystemName))
|
||||
{
|
||||
keys.Add(area.SystemName);
|
||||
}
|
||||
}
|
||||
|
||||
return keys.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
|
||||
}
|
||||
|
||||
public static List<OrderFee> CalculateQuoteReceivableFees(
|
||||
SalesProductPricePublishSnapshotDto priceSnapshot,
|
||||
string priceVersion,
|
||||
DateTime priceEffectiveTime,
|
||||
decimal chargeableWeight,
|
||||
string zoneName,
|
||||
IReadOnlyList<string> zoneKeys,
|
||||
string cargoAttribute,
|
||||
CustomerGroup? customerGroup)
|
||||
{
|
||||
@ -164,9 +204,53 @@ internal static class ParcelBillingCalculator
|
||||
priceVersion,
|
||||
priceEffectiveTime,
|
||||
chargeableWeight,
|
||||
zoneName,
|
||||
zoneKeys,
|
||||
cargoAttribute,
|
||||
customerGroup,
|
||||
onlyForecastChargingTime: false);
|
||||
}
|
||||
|
||||
public static bool HasEnabledQuotePriceItems(SalesProductPricePublishSnapshotDto priceSnapshot) =>
|
||||
priceSnapshot.Prices.Any(x => x.Status == 1);
|
||||
|
||||
public static List<OrderFee> CalculateForecastReceivableFees(
|
||||
SalesProductPricePublishSnapshotDto priceSnapshot,
|
||||
string priceVersion,
|
||||
DateTime priceEffectiveTime,
|
||||
decimal chargeableWeight,
|
||||
string zoneName,
|
||||
string cargoAttribute,
|
||||
CustomerGroup? customerGroup) =>
|
||||
CalculateForecastReceivableFees(
|
||||
priceSnapshot,
|
||||
priceVersion,
|
||||
priceEffectiveTime,
|
||||
chargeableWeight,
|
||||
new[] { zoneName },
|
||||
cargoAttribute,
|
||||
customerGroup);
|
||||
|
||||
public static List<OrderFee> CalculateForecastReceivableFees(
|
||||
SalesProductPricePublishSnapshotDto priceSnapshot,
|
||||
string priceVersion,
|
||||
DateTime priceEffectiveTime,
|
||||
decimal chargeableWeight,
|
||||
IReadOnlyList<string> zoneKeys,
|
||||
string cargoAttribute,
|
||||
CustomerGroup? customerGroup)
|
||||
{
|
||||
var items = priceSnapshot.Prices.Select(p => new BillingPriceItem(
|
||||
p.Status, p.ChargingTime, p.CostItem, p.Currency, p.Details));
|
||||
return CalculateForecastFees(
|
||||
items,
|
||||
OrderFeeTypeCodes.Receivable,
|
||||
priceVersion,
|
||||
priceEffectiveTime,
|
||||
chargeableWeight,
|
||||
zoneKeys,
|
||||
cargoAttribute,
|
||||
customerGroup,
|
||||
onlyForecastChargingTime: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -189,9 +273,10 @@ internal static class ParcelBillingCalculator
|
||||
priceVersion,
|
||||
priceEffectiveTime,
|
||||
chargeableWeight,
|
||||
zoneName,
|
||||
new[] { zoneName },
|
||||
cargoAttribute,
|
||||
customerGroup);
|
||||
customerGroup,
|
||||
onlyForecastChargingTime: true);
|
||||
}
|
||||
|
||||
public static bool HasEnabledForecastPriceItems(SalesProductPricePublishSnapshotDto priceSnapshot) =>
|
||||
@ -294,19 +379,20 @@ internal static class ParcelBillingCalculator
|
||||
string priceVersion,
|
||||
DateTime priceEffectiveTime,
|
||||
decimal chargeableWeight,
|
||||
string zoneName,
|
||||
IReadOnlyList<string> zoneKeys,
|
||||
string cargoAttribute,
|
||||
CustomerGroup? customerGroup)
|
||||
CustomerGroup? customerGroup,
|
||||
bool onlyForecastChargingTime)
|
||||
{
|
||||
var fees = new List<OrderFee>();
|
||||
foreach (var priceItem in priceItems.Where(x => x.Status == 1))
|
||||
{
|
||||
if (!IsForecastChargingTime(priceItem.ChargingTime))
|
||||
if (onlyForecastChargingTime && !IsForecastChargingTime(priceItem.ChargingTime))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var detail = MatchPriceDetail(priceItem.Details, zoneName, cargoAttribute, chargeableWeight);
|
||||
var detail = MatchPriceDetail(priceItem.Details, zoneKeys, cargoAttribute, chargeableWeight);
|
||||
if (detail == null)
|
||||
{
|
||||
continue;
|
||||
@ -343,16 +429,19 @@ internal static class ParcelBillingCalculator
|
||||
|
||||
private static ChannelPriceDetailOutput? MatchPriceDetail(
|
||||
IReadOnlyList<ChannelPriceDetailOutput> details,
|
||||
string zoneName,
|
||||
IReadOnlyList<string> zoneKeys,
|
||||
string cargoAttribute,
|
||||
decimal chargeableWeight)
|
||||
{
|
||||
var cargoLabel = GetCargoAttributeLabel(cargoAttribute);
|
||||
var tierCandidates = new List<(ChannelPriceDetailOutput Detail, decimal Tier)>();
|
||||
ChannelPriceDetailOutput? rangeHit = null;
|
||||
ChannelPriceDetailOutput? genericHit = null;
|
||||
|
||||
foreach (var detail in details)
|
||||
{
|
||||
var factors = ParseFactorDictionary(detail.FactorValuesJson);
|
||||
if (!MatchFactorValue(factors, "分区", zoneName)
|
||||
&& !MatchFactorValue(factors, "价格分区", zoneName))
|
||||
if (!ZoneMatches(factors, zoneKeys))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -363,15 +452,107 @@ internal static class ParcelBillingCalculator
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MatchWeightRange(factors, chargeableWeight))
|
||||
if (TryParseWeightRange(factors, out var rangeStart, out var rangeEnd))
|
||||
{
|
||||
if (chargeableWeight >= rangeStart && chargeableWeight <= rangeEnd)
|
||||
{
|
||||
rangeHit = detail;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return detail;
|
||||
if (TryParseSingleWeightTier(factors, out var tier))
|
||||
{
|
||||
tierCandidates.Add((detail, tier));
|
||||
continue;
|
||||
}
|
||||
|
||||
genericHit = detail;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (rangeHit != null)
|
||||
{
|
||||
return rangeHit;
|
||||
}
|
||||
|
||||
if (tierCandidates.Count > 0)
|
||||
{
|
||||
var ceiling = tierCandidates
|
||||
.Where(x => x.Tier >= chargeableWeight)
|
||||
.OrderBy(x => x.Tier)
|
||||
.Select(x => x.Detail)
|
||||
.FirstOrDefault();
|
||||
if (ceiling != null)
|
||||
{
|
||||
return ceiling;
|
||||
}
|
||||
|
||||
return tierCandidates.OrderByDescending(x => x.Tier).First().Detail;
|
||||
}
|
||||
|
||||
return genericHit;
|
||||
}
|
||||
|
||||
private static bool ZoneMatches(Dictionary<string, string> factors, IReadOnlyList<string> zoneKeys)
|
||||
{
|
||||
if (!factors.TryGetValue("分区", out var zoneValue) || string.IsNullOrWhiteSpace(zoneValue))
|
||||
{
|
||||
if (!factors.TryGetValue("价格分区", out zoneValue) || string.IsNullOrWhiteSpace(zoneValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var expected = zoneValue.Trim();
|
||||
return zoneKeys.Any(key => string.Equals(key.Trim(), expected, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private static bool TryParseWeightRange(
|
||||
Dictionary<string, string> factors,
|
||||
out decimal start,
|
||||
out decimal end)
|
||||
{
|
||||
start = 0;
|
||||
end = 0;
|
||||
if (!factors.TryGetValue("重量", out var rangeText) || string.IsNullOrWhiteSpace(rangeText))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var parts = rangeText.Split('-', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out start))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out end))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryParseSingleWeightTier(Dictionary<string, string> factors, out decimal tier)
|
||||
{
|
||||
tier = 0;
|
||||
if (!factors.TryGetValue("重量", out var text) || string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (text.Contains('-', StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return decimal.TryParse(text, NumberStyles.Any, CultureInfo.InvariantCulture, out tier);
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> ParseFactorDictionary(object? factorValuesJson)
|
||||
@ -404,23 +585,7 @@ internal static class ParcelBillingCalculator
|
||||
|
||||
private static bool MatchWeightRange(Dictionary<string, string> factors, decimal chargeableWeight)
|
||||
{
|
||||
if (!factors.TryGetValue("重量", out var rangeText) || string.IsNullOrWhiteSpace(rangeText))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var parts = rangeText.Split('-', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length != 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(parts[0], NumberStyles.Any, CultureInfo.InvariantCulture, out var start))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!decimal.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out var end))
|
||||
if (!TryParseWeightRange(factors, out var start, out var end))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -434,7 +599,7 @@ internal static class ParcelBillingCalculator
|
||||
CustomerGroup? customerGroup)
|
||||
{
|
||||
var formula = detail.CalculationFormula?.Trim() ?? string.Empty;
|
||||
if (formula is "单价" or "固定价")
|
||||
if (formula is "单价" or "固定价" or "total" or "总价")
|
||||
{
|
||||
return ResolveCustomerGroupPrice(detail, customerGroup) ?? detail.CalculatedValue ?? 0;
|
||||
}
|
||||
@ -513,7 +678,7 @@ internal static class ParcelBillingCalculator
|
||||
|
||||
private static decimal? ResolveCustomerGroupPrice(ChannelPriceDetailOutput detail, CustomerGroup? customerGroup)
|
||||
{
|
||||
if (!customerGroup.HasValue || detail.CustomerGroupValues == null)
|
||||
if (detail.CustomerGroupValues == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -525,18 +690,28 @@ internal static class ParcelBillingCalculator
|
||||
return null;
|
||||
}
|
||||
|
||||
var key = customerGroup.Value switch
|
||||
if (customerGroup.HasValue)
|
||||
{
|
||||
CustomerGroup.Vip1 => "客户组1",
|
||||
CustomerGroup.Vip2 => "客户组2",
|
||||
CustomerGroup.Vip3 => "客户组3",
|
||||
CustomerGroup.Vip4 => "客户组4",
|
||||
CustomerGroup.Vip5 => "客户组5",
|
||||
_ => null
|
||||
};
|
||||
if (key != null && map.TryGetValue(key, out var value))
|
||||
var key = customerGroup.Value switch
|
||||
{
|
||||
CustomerGroup.Normal => "普通客户",
|
||||
CustomerGroup.Vip1 => "客户组1",
|
||||
CustomerGroup.Vip2 => "客户组2",
|
||||
CustomerGroup.Vip3 => "客户组3",
|
||||
CustomerGroup.Vip4 => "客户组4",
|
||||
CustomerGroup.Vip5 => "客户组5",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (key != null && map.TryGetValue(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
if (map.TryGetValue("普通客户", out var normalPrice))
|
||||
{
|
||||
return value;
|
||||
return normalPrice;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@ -67,10 +67,11 @@ public class ParcelBillingCalculatorDemoTests
|
||||
var kpiArea = new KpiArea { Id = 1, Name = "美西分区" };
|
||||
var zoneMatched = ParcelBillingCalculator.IsAreaMatched(
|
||||
kpiRule, quoteContext.CountryCode, quoteContext.Province, quoteContext.City, quoteContext.PostalCode);
|
||||
var zoneKeys = ParcelBillingCalculator.BuildPriceZoneKeys(kpiRule, kpiArea);
|
||||
var zoneName = ParcelBillingCalculator.ResolveZoneName(kpiRule, kpiArea) ?? "美西";
|
||||
|
||||
zoneMatched.Should().BeTrue($"邮编 {quoteContext.PostalCode} 应命中美西分区");
|
||||
_output.WriteLine($"分区匹配: {zoneMatched}, 分区名: {zoneName}");
|
||||
_output.WriteLine($"分区匹配: {zoneMatched}, 分区名: {zoneName}, 分区键: {string.Join(',', zoneKeys)}");
|
||||
|
||||
// —— 3. 计重(可在 ParcelBillingCalculator.CalculateRawBillableWeight 打断点)——
|
||||
var forecastWeight = ParcelBillingCalculator.BuildForecastWeight(quoteContext);
|
||||
@ -99,7 +100,7 @@ public class ParcelBillingCalculatorDemoTests
|
||||
priceVersion,
|
||||
effectiveTime,
|
||||
chargeableWeight,
|
||||
zoneName,
|
||||
new[] { "美西" },
|
||||
quoteContext.CargoAttribute,
|
||||
null);
|
||||
|
||||
@ -127,7 +128,7 @@ public class ParcelBillingCalculatorDemoTests
|
||||
priceVersion,
|
||||
effectiveTime,
|
||||
chargeableWeight,
|
||||
zoneName,
|
||||
"美西",
|
||||
quoteContext.CargoAttribute,
|
||||
null);
|
||||
|
||||
|
||||
@ -164,6 +164,54 @@ public class ParcelBillingCalculatorTests
|
||||
fees[0].Amount.Should().Be(15M);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateQuoteReceivableFees_ShouldSupportOutboundTotalPriceTiers()
|
||||
{
|
||||
var details = new[]
|
||||
{
|
||||
ParcelBillingTestData.TotalTierDetail("3", 0.5M, 11M),
|
||||
ParcelBillingTestData.TotalTierDetail("3", 1M, 12M),
|
||||
ParcelBillingTestData.TotalTierDetail("3", 1.5M, 12M),
|
||||
ParcelBillingTestData.TotalTierDetail("3", 2M, 14M),
|
||||
};
|
||||
var snapshot = ParcelBillingTestData.BuildReceivableSnapshot(
|
||||
ParcelBillingTestData.ReceivablePriceItem("outbound", 1, "freight", details));
|
||||
|
||||
var fees = ParcelBillingCalculator.CalculateQuoteReceivableFees(
|
||||
snapshot,
|
||||
_meta.PriceVersion,
|
||||
_meta.EffectiveTime,
|
||||
1M,
|
||||
new[] { "3" },
|
||||
"general",
|
||||
null);
|
||||
|
||||
fees.Should().ContainSingle();
|
||||
fees[0].Amount.Should().Be(12M);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CalculateForecastReceivableFees_ShouldApplyNormalCustomerLabel()
|
||||
{
|
||||
var detail = ParcelBillingTestData.TotalTierDetail("3", 1M, 99M);
|
||||
detail.CustomerGroupValues = new Dictionary<string, decimal> { ["普通客户"] = 12M };
|
||||
|
||||
var snapshot = ParcelBillingTestData.BuildReceivableSnapshot(
|
||||
ParcelBillingTestData.ReceivablePriceItem("预报", 1, "freight", detail));
|
||||
|
||||
var fees = ParcelBillingCalculator.CalculateForecastReceivableFees(
|
||||
snapshot,
|
||||
_meta.PriceVersion,
|
||||
_meta.EffectiveTime,
|
||||
1M,
|
||||
new[] { "3" },
|
||||
"general",
|
||||
CustomerGroup.Normal);
|
||||
|
||||
fees.Should().ContainSingle();
|
||||
fees[0].Amount.Should().Be(12M);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HasEnabledForecastPriceItems_ShouldDetectForecastEnabledItems()
|
||||
{
|
||||
|
||||
@ -92,6 +92,22 @@ internal static class ParcelBillingTestData
|
||||
FactorValuesJson = new Dictionary<string, string> { ["分区"] = zone }
|
||||
};
|
||||
|
||||
public static ChannelPriceDetailOutput TotalTierDetail(
|
||||
string zone,
|
||||
decimal weightTier,
|
||||
decimal normalCustomerPrice) =>
|
||||
new()
|
||||
{
|
||||
CalculationFormula = "total",
|
||||
CalculatedValue = 0,
|
||||
FactorValuesJson = new Dictionary<string, string>
|
||||
{
|
||||
["分区"] = zone,
|
||||
["重量"] = weightTier.ToString(CultureInfo.InvariantCulture)
|
||||
},
|
||||
CustomerGroupValues = new Dictionary<string, decimal> { ["普通客户"] = normalCustomerPrice }
|
||||
};
|
||||
|
||||
public static WeightRuleInfo GwRule(string condition = "gw>=vw", string factor = "6000") =>
|
||||
new()
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user