fix: unify publish snapshot API output and normalize legacy price content
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e2dca20966
commit
8329bbc3a6
@ -153,7 +153,9 @@ public partial class SalesProductService
|
||||
PublishUser = entity.PublishUser,
|
||||
PublishTime = entity.PublishTime,
|
||||
Description = entity.Description,
|
||||
Content = OmsDocHelper.DeserializeJsonObject(entity.Content),
|
||||
Content = operatorType == PriceOperatorType
|
||||
? OmsDocHelper.ParseSalesProductPricePublishContentForApi(entity.Content)
|
||||
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeRulesSnapshot(entity.Content)),
|
||||
CreateTime = entity.CreateTime
|
||||
}).ToList(),
|
||||
Total = total
|
||||
|
||||
@ -335,9 +335,10 @@ public partial class SalesProductService : ApplicationService
|
||||
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int salesProductId, [FromQuery] string operatorType)
|
||||
{
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
return normalized == PriceOperatorType
|
||||
object snapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(salesProductId)
|
||||
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||
return OmsDocHelper.ToSnapshotApiContent(snapshot)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -168,7 +168,9 @@ public partial class ServiceChannelService
|
||||
PublishUser = entity.PublishUser,
|
||||
PublishTime = entity.PublishTime,
|
||||
Description = entity.Description,
|
||||
Content = OmsDocHelper.DeserializeJsonObject(entity.Content),
|
||||
Content = operatorType == PriceOperatorType
|
||||
? OmsDocHelper.ParseChannelPricePublishContentForApi(entity.Content)
|
||||
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshotDto>(entity.Content)),
|
||||
CreateTime = entity.CreateTime
|
||||
}).ToList(),
|
||||
Total = total
|
||||
|
||||
@ -334,9 +334,10 @@ public partial class ServiceChannelService : ApplicationService
|
||||
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int serviceChannelId, [FromQuery] string operatorType)
|
||||
{
|
||||
var normalized = NormalizeOperatorType(operatorType);
|
||||
return normalized == PriceOperatorType
|
||||
object snapshot = normalized == PriceOperatorType
|
||||
? await BuildPricePublishSnapshotAsync(serviceChannelId)
|
||||
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
||||
return OmsDocHelper.ToSnapshotApiContent(snapshot)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -802,7 +802,7 @@ public partial class OrderService
|
||||
bindTime);
|
||||
if (priceRecord?.Content != null)
|
||||
{
|
||||
var snapshot = OmsDocHelper.DeserializeJson<ServiceChannelPricePublishSnapshotDto>(priceRecord.Content);
|
||||
var snapshot = OmsDocHelper.DeserializeChannelPriceSnapshot(priceRecord.Content);
|
||||
if (snapshot?.Prices.Count > 0)
|
||||
{
|
||||
return new ChannelPriceBinding
|
||||
|
||||
@ -115,6 +115,20 @@ public static class OmsDocHelper
|
||||
return JsonSerializer.Serialize(value, SnapshotJsonOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将快照对象转为 API 输出结构(与落库 JSON 同构,避免预览/列表/落库序列化不一致)。
|
||||
/// </summary>
|
||||
public static object? ToSnapshotApiContent<T>(T? value)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var json = SerializeSnapshotJson(value);
|
||||
return string.IsNullOrWhiteSpace(json) ? null : DeserializeJsonObject(json);
|
||||
}
|
||||
|
||||
public static SalesProductRulesPublishSnapshotDto? DeserializeRulesSnapshot(string? json)
|
||||
{
|
||||
var snapshot = DeserializeJson<SalesProductRulesPublishSnapshotDto>(json);
|
||||
@ -129,6 +143,31 @@ public static class OmsDocHelper
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
public static ServiceChannelPricePublishSnapshotDto? DeserializeChannelPriceSnapshot(string? json)
|
||||
{
|
||||
var snapshot = DeserializeJson<ServiceChannelPricePublishSnapshotDto>(json);
|
||||
PublishSnapshotNormalizer.NormalizeChannelPriceSnapshot(snapshot);
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析渠道买价发布 Content,供列表/对比 API 使用(兼容旧快照审计字段)。
|
||||
/// </summary>
|
||||
public static object? ParseChannelPricePublishContentForApi(string? json)
|
||||
{
|
||||
var snapshot = DeserializeChannelPriceSnapshot(json);
|
||||
return snapshot is null ? DeserializeJsonObject(json) : ToSnapshotApiContent(snapshot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析产品销售卖价发布 Content,供列表/对比 API 使用。
|
||||
/// </summary>
|
||||
public static object? ParseSalesProductPricePublishContentForApi(string? json)
|
||||
{
|
||||
var snapshot = DeserializePriceSnapshot(json);
|
||||
return snapshot is null ? DeserializeJsonObject(json) : ToSnapshotApiContent(snapshot);
|
||||
}
|
||||
|
||||
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
|
||||
{
|
||||
JsonValueKind.Object => element.EnumerateObject()
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
using Oms.Application.Dtos.Channels;
|
||||
using Oms.Application.Dtos.Orders;
|
||||
|
||||
namespace Oms.Application.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 发布快照读取后规范化(旧字段迁移、去掉冗余)。
|
||||
/// 发布快照读取后规范化(旧字段迁移、去掉冗余、稳定排序)。
|
||||
/// </summary>
|
||||
public static class PublishSnapshotNormalizer
|
||||
{
|
||||
@ -27,6 +28,17 @@ public static class PublishSnapshotNormalizer
|
||||
}
|
||||
|
||||
NormalizeKpiAreas(snapshot.PriceAreaInfos);
|
||||
snapshot.Prices = SortSalesProductPriceItems(snapshot.Prices);
|
||||
}
|
||||
|
||||
public static void NormalizeChannelPriceSnapshot(ServiceChannelPricePublishSnapshotDto? snapshot)
|
||||
{
|
||||
if (snapshot is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
snapshot.Prices = SortChannelPriceItems(snapshot.Prices);
|
||||
}
|
||||
|
||||
public static void NormalizeKpiAreas(List<KpiAreaSnapshotRuleDto>? areas)
|
||||
@ -78,4 +90,41 @@ public static class PublishSnapshotNormalizer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SalesProductPriceSnapshotItemDto> SortSalesProductPriceItems(
|
||||
List<SalesProductPriceSnapshotItemDto>? prices) =>
|
||||
prices?
|
||||
.OrderBy(price => price.SerialNumber)
|
||||
.ThenBy(price => price.Id)
|
||||
.Select(price =>
|
||||
{
|
||||
price.Factors = SortFactors(price.Factors);
|
||||
price.Details = SortDetails(price.Details);
|
||||
return price;
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
private static List<ServiceChannelPriceSnapshotItemDto> SortChannelPriceItems(
|
||||
List<ServiceChannelPriceSnapshotItemDto>? prices) =>
|
||||
prices?
|
||||
.OrderBy(price => price.SerialNumber)
|
||||
.ThenBy(price => price.Id)
|
||||
.Select(price =>
|
||||
{
|
||||
price.Factors = SortFactors(price.Factors);
|
||||
price.Details = SortDetails(price.Details);
|
||||
return price;
|
||||
})
|
||||
.ToList() ?? [];
|
||||
|
||||
private static List<ChannelPriceFactorOutput> SortFactors(List<ChannelPriceFactorOutput>? factors) =>
|
||||
factors?
|
||||
.OrderBy(factor => factor.SerialNumber)
|
||||
.ThenBy(factor => factor.Id)
|
||||
.ToList() ?? [];
|
||||
|
||||
private static List<ChannelPriceDetailOutput> SortDetails(List<ChannelPriceDetailOutput>? details) =>
|
||||
details?
|
||||
.OrderBy(detail => detail.Id)
|
||||
.ToList() ?? [];
|
||||
}
|
||||
|
||||
105
src/Oms.Tests/PublishSnapshotCompareTests.cs
Normal file
105
src/Oms.Tests/PublishSnapshotCompareTests.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using FluentAssertions;
|
||||
using Oms.Application.Shared;
|
||||
|
||||
namespace Oms.Tests;
|
||||
|
||||
public class PublishSnapshotCompareTests
|
||||
{
|
||||
private const string LegacyOnlineJson = """
|
||||
{
|
||||
"prices": [
|
||||
{
|
||||
"id": 1,
|
||||
"status": 2,
|
||||
"details": [
|
||||
{
|
||||
"id": 6,
|
||||
"priceId": 1,
|
||||
"calculatedValue": 0,
|
||||
"factorValuesJson": { "分区": "3", "重量": "(0-0.5]" },
|
||||
"calculationFormula": "total",
|
||||
"customerGroupValues": { "普通客户": 11 }
|
||||
}
|
||||
],
|
||||
"factors": [
|
||||
{ "id": 7, "factor": "zone", "priceId": 1, "factorValues": ["3"], "serialNumber": 1 },
|
||||
{ "id": 8, "factor": "weight", "priceId": 1, "factorValues": ["0.5"], "serialNumber": 2 },
|
||||
{ "id": 9, "factor": "customer", "priceId": 1, "factorValues": ["0"], "serialNumber": 3 }
|
||||
],
|
||||
"costItem": "freight",
|
||||
"costUnit": "kg",
|
||||
"currency": "RMB",
|
||||
"priceName": "运费",
|
||||
"createTime": "0001-01-01T00:00:00",
|
||||
"serialNumber": 1,
|
||||
"serviceChannelId": 1,
|
||||
"chargingCondition": "无"
|
||||
}
|
||||
],
|
||||
"serviceChannelId": 1
|
||||
}
|
||||
""";
|
||||
|
||||
private const string PreviewJson = """
|
||||
{
|
||||
"serviceChannelId": 1,
|
||||
"prices": [
|
||||
{
|
||||
"id": 1,
|
||||
"serviceChannelId": 1,
|
||||
"serialNumber": 1,
|
||||
"status": 2,
|
||||
"priceName": "运费",
|
||||
"costItem": "freight",
|
||||
"costUnit": "kg",
|
||||
"currency": "RMB",
|
||||
"chargingCondition": "无",
|
||||
"factors": [
|
||||
{ "id": 7, "priceId": 1, "serialNumber": 1, "factor": "zone", "factorValues": ["3"] },
|
||||
{ "id": 8, "priceId": 1, "serialNumber": 2, "factor": "weight", "factorValues": ["0.5"] },
|
||||
{ "id": 9, "priceId": 1, "serialNumber": 3, "factor": "customer", "factorValues": ["0"] }
|
||||
],
|
||||
"details": [
|
||||
{
|
||||
"id": 6,
|
||||
"priceId": 1,
|
||||
"factorValuesJson": { "分区": "3", "重量": "(0-0.5]" },
|
||||
"calculationFormula": "total",
|
||||
"calculatedValue": 0,
|
||||
"customerGroupValues": { "普通客户": 11 }
|
||||
}
|
||||
],
|
||||
"createTime": "0001-01-01 00:00:00",
|
||||
"updateTime": null
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
[Fact]
|
||||
public void ParseChannelPricePublishContentForApi_ShouldMatchPreviewAfterLegacyAuditNoise()
|
||||
{
|
||||
var online = OmsDocHelper.ParseChannelPricePublishContentForApi(LegacyOnlineJson);
|
||||
var preview = OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeChannelPriceSnapshot(PreviewJson));
|
||||
|
||||
var onlineJson = OmsDocHelper.SerializeSnapshotJson(online);
|
||||
var previewJson = OmsDocHelper.SerializeSnapshotJson(preview);
|
||||
|
||||
onlineJson.Should().Be(previewJson);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToSnapshotApiContent_ShouldRoundTripWithPublishSerializer()
|
||||
{
|
||||
var snapshot = OmsDocHelper.DeserializeChannelPriceSnapshot(PreviewJson);
|
||||
snapshot.Should().NotBeNull();
|
||||
|
||||
var json = OmsDocHelper.SerializeSnapshotJson(snapshot);
|
||||
json.Should().NotContain("createTime");
|
||||
json.Should().NotContain("updateTime");
|
||||
|
||||
var apiContent = OmsDocHelper.ToSnapshotApiContent(snapshot);
|
||||
var roundTripJson = OmsDocHelper.SerializeSnapshotJson(apiContent);
|
||||
roundTripJson.Should().Be(json);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user