fix: 计重进位快照兼容与 KPI 分区明细 Excel 导入
- 发布规则快照反序列化合并 StartValue/EndValue 至 Start/End - 小包创建进位时快照区间无效则降级读 t_weight_rounding_info - 新增 KPI 分区明细导入模板下载与 Excel 批量导入接口 - 补充快照进位与导入相关单元测试 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7827b4803e
commit
91ba6d5d59
19
src/Oms.Application/Dtos/Channels/KpiAreaInfoImportInput.cs
Normal file
19
src/Oms.Application/Dtos/Channels/KpiAreaInfoImportInput.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Oms.Application.Dtos.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// KPI 分区明细 Excel 导入入参。
|
||||
/// </summary>
|
||||
public class KpiAreaInfoImportInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分区主键。
|
||||
/// </summary>
|
||||
public int AreaId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Excel 文件。
|
||||
/// </summary>
|
||||
public IFormFile File { get; set; } = null!;
|
||||
}
|
||||
23
src/Oms.Application/Dtos/Channels/KpiAreaInfoImportOutput.cs
Normal file
23
src/Oms.Application/Dtos/Channels/KpiAreaInfoImportOutput.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Oms.Application.Dtos.Channels;
|
||||
|
||||
/// <summary>
|
||||
/// KPI 分区明细 Excel 导入结果。
|
||||
/// </summary>
|
||||
public class KpiAreaInfoImportOutput
|
||||
{
|
||||
public int SuccessCount { get; set; }
|
||||
|
||||
public int FailCount { get; set; }
|
||||
|
||||
public List<KpiAreaInfoImportFailRow> Failures { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入失败行。
|
||||
/// </summary>
|
||||
public class KpiAreaInfoImportFailRow
|
||||
{
|
||||
public int RowNumber { get; set; }
|
||||
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
}
|
||||
@ -55,6 +55,14 @@ public class WeightRoundingSnapshotRangeDto
|
||||
public decimal End { get; set; }
|
||||
|
||||
public string? Public { get; set; }
|
||||
|
||||
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.StartValue);读取后由 <see cref="PublishSnapshotNormalizer"/> 合并至 <see cref="Start"/>。</summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public decimal StartValue { get; set; }
|
||||
|
||||
/// <summary>旧快照/明细 API 兼容(t_weight_rounding_info.EndValue);读取后由 <see cref="PublishSnapshotNormalizer"/> 合并至 <see cref="End"/>。</summary>
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
|
||||
public decimal EndValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
using System.Linq.Expressions;
|
||||
using Fake.Application;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Oms.Application.Dtos.Channels;
|
||||
using Oms.Application.Dtos.Shared;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.ChannelsAggregate.Entities;
|
||||
using Oms.Domain.ChannelsAggregate.Repositories;
|
||||
using Oms.Domain.SystemAggregate.Repositories;
|
||||
|
||||
namespace Oms.Application.Services.Channels;
|
||||
|
||||
@ -17,6 +19,8 @@ public class KpiAreaService : ApplicationService
|
||||
{
|
||||
private IKpiAreaRepository KpiAreaRepository => LazyServiceProvider.GetRequiredService<IKpiAreaRepository>();
|
||||
|
||||
private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService<ICountryRepository>();
|
||||
|
||||
/// <summary>
|
||||
/// 查询KPI分区列表。
|
||||
/// </summary>
|
||||
@ -210,6 +214,69 @@ public class KpiAreaService : ApplicationService
|
||||
await KpiAreaRepository.DeleteInfoAsync(input.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下载 KPI 分区明细导入模板。
|
||||
/// </summary>
|
||||
[HttpGet("/channel/kpi-area/info/import-template")]
|
||||
public async Task<FileContentResult> DownloadInfoImportTemplateAsync([FromQuery] string? areaType)
|
||||
{
|
||||
var bytes = await KpiAreaInfoImportHelper.BuildTemplateBytesAsync();
|
||||
return new FileContentResult(bytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
||||
{
|
||||
FileDownloadName = KpiAreaInfoImportHelper.ResolveTemplateFileName(areaType)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入 KPI 分区明细(Excel)。
|
||||
/// </summary>
|
||||
[HttpPost("/channel/kpi-area/info/import")]
|
||||
public async Task<KpiAreaInfoImportOutput> ImportInfoAsync([FromForm] KpiAreaInfoImportInput input)
|
||||
{
|
||||
if (input.File == null || input.File.Length == 0)
|
||||
{
|
||||
throw new BusinessException("请上传 Excel 文件");
|
||||
}
|
||||
|
||||
await GetAreaOrThrowAsync(input.AreaId);
|
||||
|
||||
await using var stream = input.File.OpenReadStream();
|
||||
var parsedRows = await KpiAreaInfoImportHelper.ParseImportRowsAsync(stream);
|
||||
if (parsedRows.Count == 0)
|
||||
{
|
||||
throw new BusinessException("Excel 中无有效数据行");
|
||||
}
|
||||
|
||||
var countries = await CountryRepository.GetListAsync();
|
||||
var countriesByCode = countries
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.CountryCode))
|
||||
.GroupBy(x => x.CountryCode.Trim(), StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(g => g.Key, g => g.First(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var output = new KpiAreaInfoImportOutput();
|
||||
foreach (var (rowNumber, entity) in parsedRows)
|
||||
{
|
||||
try
|
||||
{
|
||||
KpiAreaInfoImportHelper.PrepareEntityForImport(entity, countriesByCode);
|
||||
entity.AreaId = input.AreaId;
|
||||
await KpiAreaRepository.InsertInfoAsync(entity);
|
||||
output.SuccessCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
output.FailCount++;
|
||||
output.Failures.Add(new KpiAreaInfoImportFailRow
|
||||
{
|
||||
RowNumber = rowNumber,
|
||||
Reason = ex.Message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static Expression<Func<KpiArea, bool>> BuildAreaPredicate(KpiAreaListInput input)
|
||||
{
|
||||
var appKey = input.AppKey?.Trim();
|
||||
|
||||
@ -597,10 +597,13 @@ public partial class OrderService
|
||||
throw new BusinessException("未配置计重进位");
|
||||
}
|
||||
|
||||
var snapshotRanges = rulesSnapshot?.WeightRoundingInfos;
|
||||
var fromSnapshot = snapshotRanges is { Count: > 0 }
|
||||
&& snapshotRanges.Any(x => !string.IsNullOrWhiteSpace(x.Public));
|
||||
IReadOnlyList<WeightRoundingInfo> ranges;
|
||||
if (rulesSnapshot?.WeightRoundingInfos is { Count: > 0 })
|
||||
if (fromSnapshot)
|
||||
{
|
||||
ranges = RulesSnapshotHelper.ToWeightRoundingEntities(rulesSnapshot.WeightRoundingInfos);
|
||||
ranges = RulesSnapshotHelper.ToWeightRoundingEntities(snapshotRanges!);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -613,8 +616,21 @@ public partial class OrderService
|
||||
ranges = await WeightRoundingRepository.GetAllInfosByRoundingIdAsync(weightRoundingId.Value);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
forecast.ChargeableWeight = ParcelBillingCalculator.ApplyRounding(rawWeight, ranges);
|
||||
}
|
||||
catch (InvalidOperationException) when (fromSnapshot)
|
||||
{
|
||||
var dbRanges = await WeightRoundingRepository.GetAllInfosByRoundingIdAsync(weightRoundingId.Value);
|
||||
if (dbRanges.Count == 0)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
forecast.ChargeableWeight = ParcelBillingCalculator.ApplyRounding(rawWeight, dbRanges);
|
||||
}
|
||||
}
|
||||
catch (BusinessException)
|
||||
{
|
||||
throw;
|
||||
|
||||
230
src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs
Normal file
230
src/Oms.Application/Shared/KpiAreaInfoImportHelper.cs
Normal file
@ -0,0 +1,230 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using MiniExcelLibs;
|
||||
using Oms.Application.Dtos.Channels;
|
||||
using Oms.Domain.ChannelsAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Entities;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// KPI 分区明细 Excel 模板与导入解析。
|
||||
/// </summary>
|
||||
public static class KpiAreaInfoImportHelper
|
||||
{
|
||||
private static readonly Regex ChinesePattern = new(@"[\u4E00-\u9FA5]", RegexOptions.Compiled);
|
||||
|
||||
private static readonly Dictionary<string, string> HeaderAliases = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["国家代码"] = nameof(KpiAreaInfo.CountryCode),
|
||||
["CountryCode"] = nameof(KpiAreaInfo.CountryCode),
|
||||
["countryCode"] = nameof(KpiAreaInfo.CountryCode),
|
||||
["国家名称"] = nameof(KpiAreaInfo.CountryName),
|
||||
["CountryName"] = nameof(KpiAreaInfo.CountryName),
|
||||
["countryName"] = nameof(KpiAreaInfo.CountryName),
|
||||
["州/省"] = nameof(KpiAreaInfo.Province),
|
||||
["州省"] = nameof(KpiAreaInfo.Province),
|
||||
["Province"] = nameof(KpiAreaInfo.Province),
|
||||
["province"] = nameof(KpiAreaInfo.Province),
|
||||
["城市"] = nameof(KpiAreaInfo.City),
|
||||
["City"] = nameof(KpiAreaInfo.City),
|
||||
["city"] = nameof(KpiAreaInfo.City),
|
||||
["起始邮编"] = nameof(KpiAreaInfo.LeftPostcode),
|
||||
["LeftPostcode"] = nameof(KpiAreaInfo.LeftPostcode),
|
||||
["leftPostcode"] = nameof(KpiAreaInfo.LeftPostcode),
|
||||
["leftPostCode"] = nameof(KpiAreaInfo.LeftPostcode),
|
||||
["截止邮编"] = nameof(KpiAreaInfo.RightPostcode),
|
||||
["RightPostcode"] = nameof(KpiAreaInfo.RightPostcode),
|
||||
["rightPostcode"] = nameof(KpiAreaInfo.RightPostcode),
|
||||
["rightPostCode"] = nameof(KpiAreaInfo.RightPostcode),
|
||||
};
|
||||
|
||||
public static async Task<byte[]> BuildTemplateBytesAsync()
|
||||
{
|
||||
var rows = new List<Dictionary<string, object?>>
|
||||
{
|
||||
new()
|
||||
{
|
||||
["国家代码"] = "US",
|
||||
["国家名称"] = "美国",
|
||||
["州/省"] = "CA",
|
||||
["城市"] = "",
|
||||
["起始邮编"] = "90000",
|
||||
["截止邮编"] = "90199"
|
||||
}
|
||||
};
|
||||
|
||||
await using var stream = new MemoryStream();
|
||||
await MiniExcel.SaveAsAsync(stream, rows);
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
public static string ResolveTemplateFileName(string? areaType) =>
|
||||
areaType?.Trim().ToLowerInvariant() switch
|
||||
{
|
||||
"blacklist" => "黑名单分区明细导入模板.xlsx",
|
||||
"remote" => "偏远分区明细导入模板.xlsx",
|
||||
"price" => "价格分区明细导入模板.xlsx",
|
||||
_ => "分区明细导入模板.xlsx"
|
||||
};
|
||||
|
||||
public static async Task<List<(int RowNumber, KpiAreaInfo Entity)>> ParseImportRowsAsync(Stream fileStream)
|
||||
{
|
||||
var rows = await MiniExcel.QueryAsync(fileStream, useHeaderRow: true);
|
||||
var result = new List<(int RowNumber, KpiAreaInfo Entity)>();
|
||||
var rowNumber = 1;
|
||||
|
||||
foreach (var row in rows)
|
||||
{
|
||||
rowNumber++;
|
||||
if (row is not IDictionary<string, object> rowDict)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsEmptyRow(rowDict))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add((rowNumber, ParseRow(rowDict)));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void PrepareEntityForImport(
|
||||
KpiAreaInfo entity,
|
||||
IReadOnlyDictionary<string, Country> countriesByCode)
|
||||
{
|
||||
ValidateEntity(entity);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.CountryName)
|
||||
&& !string.IsNullOrWhiteSpace(entity.CountryCode))
|
||||
{
|
||||
var code = entity.CountryCode.Trim();
|
||||
if (countriesByCode.TryGetValue(code, out var country))
|
||||
{
|
||||
entity.CountryName = country.ChineseName;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(entity.CountryName))
|
||||
{
|
||||
throw new InvalidOperationException("国家名称不能为空,请填写国家名称或有效的国家代码");
|
||||
}
|
||||
}
|
||||
|
||||
private static KpiAreaInfo ParseRow(IDictionary<string, object> rowDict)
|
||||
{
|
||||
var mapped = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var (header, value) in rowDict)
|
||||
{
|
||||
if (!HeaderAliases.TryGetValue(header.Trim(), out var field))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
mapped[field] = NormalizeCell(value);
|
||||
}
|
||||
|
||||
return new KpiAreaInfo
|
||||
{
|
||||
CountryCode = mapped.GetValueOrDefault(nameof(KpiAreaInfo.CountryCode)),
|
||||
CountryName = mapped.GetValueOrDefault(nameof(KpiAreaInfo.CountryName)),
|
||||
Province = mapped.GetValueOrDefault(nameof(KpiAreaInfo.Province)),
|
||||
City = mapped.GetValueOrDefault(nameof(KpiAreaInfo.City)),
|
||||
LeftPostcode = mapped.GetValueOrDefault(nameof(KpiAreaInfo.LeftPostcode)),
|
||||
RightPostcode = mapped.GetValueOrDefault(nameof(KpiAreaInfo.RightPostcode))
|
||||
};
|
||||
}
|
||||
|
||||
private static void ValidateEntity(KpiAreaInfo entity)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entity.CountryCode))
|
||||
{
|
||||
throw new InvalidOperationException("国家代码不能为空");
|
||||
}
|
||||
|
||||
entity.CountryCode = entity.CountryCode.Trim();
|
||||
if (entity.CountryCode.Length > 16)
|
||||
{
|
||||
throw new InvalidOperationException("国家代码不可超过16个字符");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entity.CountryName))
|
||||
{
|
||||
entity.CountryName = entity.CountryName.Trim();
|
||||
if (entity.CountryName.Length > 64)
|
||||
{
|
||||
throw new InvalidOperationException("国家名称不可超过64个字符");
|
||||
}
|
||||
}
|
||||
|
||||
entity.Province = TrimOrNull(entity.Province);
|
||||
entity.City = TrimOrNull(entity.City);
|
||||
entity.LeftPostcode = TrimOrNull(entity.LeftPostcode);
|
||||
entity.RightPostcode = TrimOrNull(entity.RightPostcode);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entity.Province) && entity.Province.Length > 128)
|
||||
{
|
||||
throw new InvalidOperationException("州/省不可超过128个字符");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entity.City) && entity.City.Length > 128)
|
||||
{
|
||||
throw new InvalidOperationException("城市不可超过128个字符");
|
||||
}
|
||||
|
||||
ValidatePostcode(entity.LeftPostcode, "起始邮编");
|
||||
ValidatePostcode(entity.RightPostcode, "截止邮编");
|
||||
|
||||
var hasLeft = !string.IsNullOrWhiteSpace(entity.LeftPostcode);
|
||||
var hasRight = !string.IsNullOrWhiteSpace(entity.RightPostcode);
|
||||
if (hasLeft != hasRight)
|
||||
{
|
||||
throw new InvalidOperationException("起始邮编与截止邮编需同时填写或同时留空");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidatePostcode(string? value, string label)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ChinesePattern.IsMatch(value))
|
||||
{
|
||||
throw new InvalidOperationException($"{label}不能包含中文");
|
||||
}
|
||||
|
||||
if (value.Length > 30)
|
||||
{
|
||||
throw new InvalidOperationException($"{label}不可超过30个字符");
|
||||
}
|
||||
}
|
||||
|
||||
private static string? TrimOrNull(string? value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.Trim();
|
||||
}
|
||||
|
||||
private static string? NormalizeCell(object? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)?.Trim();
|
||||
return string.IsNullOrWhiteSpace(text) ? null : text;
|
||||
}
|
||||
|
||||
private static bool IsEmptyRow(IDictionary<string, object> rowDict) =>
|
||||
rowDict.Values.All(value => string.IsNullOrWhiteSpace(NormalizeCell(value)));
|
||||
}
|
||||
@ -16,6 +16,7 @@ public static class PublishSnapshotNormalizer
|
||||
|
||||
NormalizeKpiAreas(snapshot.BlacklistAreaInfos);
|
||||
NormalizeKpiAreas(snapshot.RemoteAreaInfos);
|
||||
NormalizeWeightRoundingRanges(snapshot.WeightRoundingInfos);
|
||||
}
|
||||
|
||||
public static void NormalizePriceSnapshot(SalesProductPricePublishSnapshotDto? snapshot)
|
||||
@ -47,4 +48,34 @@ public static class PublishSnapshotNormalizer
|
||||
area.RightPostcode = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void NormalizeWeightRoundingRanges(List<WeightRoundingSnapshotRangeDto>? ranges)
|
||||
{
|
||||
if (ranges is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var range in ranges)
|
||||
{
|
||||
if (range.StartValue != 0)
|
||||
{
|
||||
range.Start = range.StartValue;
|
||||
}
|
||||
|
||||
if (range.EndValue != 0)
|
||||
{
|
||||
range.End = range.EndValue;
|
||||
}
|
||||
|
||||
range.StartValue = 0;
|
||||
range.EndValue = 0;
|
||||
|
||||
if (range.Start == 0 && range.End == 0 && !string.IsNullOrWhiteSpace(range.Public))
|
||||
{
|
||||
// 无效区间占位,避免误用导致「计重进位区间未匹配」;后续会降级读 DB。
|
||||
range.Public = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
src/Oms.Tests/KpiAreaInfoImportHelperTests.cs
Normal file
43
src/Oms.Tests/KpiAreaInfoImportHelperTests.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using FluentAssertions;
|
||||
using Oms.Application.Shared;
|
||||
using Oms.Domain.ChannelsAggregate.Entities;
|
||||
using Oms.Domain.SystemAggregate.Entities;
|
||||
|
||||
namespace Oms.Tests;
|
||||
|
||||
public class KpiAreaInfoImportHelperTests
|
||||
{
|
||||
[Fact]
|
||||
public void PrepareEntityForImport_ValidRow_ShouldFillCountryName()
|
||||
{
|
||||
var entity = new KpiAreaInfo
|
||||
{
|
||||
CountryCode = "US",
|
||||
LeftPostcode = "90000",
|
||||
RightPostcode = "90199"
|
||||
};
|
||||
var countries = new Dictionary<string, Country>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["US"] = new Country { CountryCode = "US", ChineseName = "美国" }
|
||||
};
|
||||
|
||||
KpiAreaInfoImportHelper.PrepareEntityForImport(entity, countries);
|
||||
|
||||
entity.CountryName.Should().Be("美国");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrepareEntityForImport_MissingPostcodePair_ShouldThrow()
|
||||
{
|
||||
var entity = new KpiAreaInfo
|
||||
{
|
||||
CountryCode = "US",
|
||||
CountryName = "美国",
|
||||
LeftPostcode = "90000"
|
||||
};
|
||||
|
||||
var act = () => KpiAreaInfoImportHelper.PrepareEntityForImport(entity, new Dictionary<string, Country>());
|
||||
|
||||
act.Should().Throw<InvalidOperationException>().WithMessage("*截止邮编*");
|
||||
}
|
||||
}
|
||||
@ -121,4 +121,34 @@ public class RulesSnapshotVerificationTests
|
||||
rawWeight.Should().Be(0.5M);
|
||||
chargeable.Should().Be(0.5M);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeserializeRulesSnapshot_StartValueEndValue_ShouldApplyRoundingFor12kg()
|
||||
{
|
||||
const string json = """
|
||||
{
|
||||
"WeightRoundingId": 1,
|
||||
"WeightRoundingInfos": [
|
||||
{ "Id": 1, "StartValue": 0, "EndValue": 99, "Public": "0.5" }
|
||||
],
|
||||
"WeightRuleInfos": [
|
||||
{ "Id": 1, "VolumetricFactor": "8000", "Condition": "gw>vw", "BillableWeight": "gw" }
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var snapshot = OmsDocHelper.DeserializeRulesSnapshot(json);
|
||||
snapshot.Should().NotBeNull();
|
||||
snapshot!.WeightRoundingInfos[0].Start.Should().Be(0);
|
||||
snapshot.WeightRoundingInfos[0].End.Should().Be(99);
|
||||
|
||||
var forecast = new OrderWeight { Length = 10, Width = 10, Height = 10, ActualWeight = 1.2M };
|
||||
var ruleInfos = RulesSnapshotHelper.ToWeightRuleEntities(snapshot.WeightRuleInfos);
|
||||
var (_, rawWeight) = ParcelBillingCalculator.CalculateRawBillableWeight(forecast, ruleInfos);
|
||||
var ranges = RulesSnapshotHelper.ToWeightRoundingEntities(snapshot.WeightRoundingInfos);
|
||||
var chargeable = ParcelBillingCalculator.ApplyRounding(rawWeight, ranges);
|
||||
|
||||
rawWeight.Should().Be(1.2M);
|
||||
chargeable.Should().Be(1.5M);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user