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,
|
PublishUser = entity.PublishUser,
|
||||||
PublishTime = entity.PublishTime,
|
PublishTime = entity.PublishTime,
|
||||||
Description = entity.Description,
|
Description = entity.Description,
|
||||||
Content = OmsDocHelper.DeserializeJsonObject(entity.Content),
|
Content = operatorType == PriceOperatorType
|
||||||
|
? OmsDocHelper.ParseSalesProductPricePublishContentForApi(entity.Content)
|
||||||
|
: OmsDocHelper.ToSnapshotApiContent(OmsDocHelper.DeserializeRulesSnapshot(entity.Content)),
|
||||||
CreateTime = entity.CreateTime
|
CreateTime = entity.CreateTime
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Total = total
|
Total = total
|
||||||
|
|||||||
@ -335,9 +335,10 @@ public partial class SalesProductService : ApplicationService
|
|||||||
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int salesProductId, [FromQuery] string operatorType)
|
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int salesProductId, [FromQuery] string operatorType)
|
||||||
{
|
{
|
||||||
var normalized = NormalizeOperatorType(operatorType);
|
var normalized = NormalizeOperatorType(operatorType);
|
||||||
return normalized == PriceOperatorType
|
object snapshot = normalized == PriceOperatorType
|
||||||
? await BuildPricePublishSnapshotAsync(salesProductId)
|
? await BuildPricePublishSnapshotAsync(salesProductId)
|
||||||
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
: await BuildRulesPublishSnapshotAsync(salesProductId);
|
||||||
|
return OmsDocHelper.ToSnapshotApiContent(snapshot)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -168,7 +168,9 @@ public partial class ServiceChannelService
|
|||||||
PublishUser = entity.PublishUser,
|
PublishUser = entity.PublishUser,
|
||||||
PublishTime = entity.PublishTime,
|
PublishTime = entity.PublishTime,
|
||||||
Description = entity.Description,
|
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
|
CreateTime = entity.CreateTime
|
||||||
}).ToList(),
|
}).ToList(),
|
||||||
Total = total
|
Total = total
|
||||||
|
|||||||
@ -334,9 +334,10 @@ public partial class ServiceChannelService : ApplicationService
|
|||||||
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int serviceChannelId, [FromQuery] string operatorType)
|
public async Task<object> PreviewPublishSnapshotAsync([FromQuery] int serviceChannelId, [FromQuery] string operatorType)
|
||||||
{
|
{
|
||||||
var normalized = NormalizeOperatorType(operatorType);
|
var normalized = NormalizeOperatorType(operatorType);
|
||||||
return normalized == PriceOperatorType
|
object snapshot = normalized == PriceOperatorType
|
||||||
? await BuildPricePublishSnapshotAsync(serviceChannelId)
|
? await BuildPricePublishSnapshotAsync(serviceChannelId)
|
||||||
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
: await BuildRulesPublishSnapshotAsync(serviceChannelId);
|
||||||
|
return OmsDocHelper.ToSnapshotApiContent(snapshot)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -802,7 +802,7 @@ public partial class OrderService
|
|||||||
bindTime);
|
bindTime);
|
||||||
if (priceRecord?.Content != null)
|
if (priceRecord?.Content != null)
|
||||||
{
|
{
|
||||||
var snapshot = OmsDocHelper.DeserializeJson<ServiceChannelPricePublishSnapshotDto>(priceRecord.Content);
|
var snapshot = OmsDocHelper.DeserializeChannelPriceSnapshot(priceRecord.Content);
|
||||||
if (snapshot?.Prices.Count > 0)
|
if (snapshot?.Prices.Count > 0)
|
||||||
{
|
{
|
||||||
return new ChannelPriceBinding
|
return new ChannelPriceBinding
|
||||||
|
|||||||
@ -115,6 +115,20 @@ public static class OmsDocHelper
|
|||||||
return JsonSerializer.Serialize(value, SnapshotJsonOptions);
|
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)
|
public static SalesProductRulesPublishSnapshotDto? DeserializeRulesSnapshot(string? json)
|
||||||
{
|
{
|
||||||
var snapshot = DeserializeJson<SalesProductRulesPublishSnapshotDto>(json);
|
var snapshot = DeserializeJson<SalesProductRulesPublishSnapshotDto>(json);
|
||||||
@ -129,6 +143,31 @@ public static class OmsDocHelper
|
|||||||
return snapshot;
|
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
|
private static object? ConvertJsonElement(JsonElement element) => element.ValueKind switch
|
||||||
{
|
{
|
||||||
JsonValueKind.Object => element.EnumerateObject()
|
JsonValueKind.Object => element.EnumerateObject()
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
|
using Oms.Application.Dtos.Channels;
|
||||||
using Oms.Application.Dtos.Orders;
|
using Oms.Application.Dtos.Orders;
|
||||||
|
|
||||||
namespace Oms.Application.Shared;
|
namespace Oms.Application.Shared;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 发布快照读取后规范化(旧字段迁移、去掉冗余)。
|
/// 发布快照读取后规范化(旧字段迁移、去掉冗余、稳定排序)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PublishSnapshotNormalizer
|
public static class PublishSnapshotNormalizer
|
||||||
{
|
{
|
||||||
@ -27,6 +28,17 @@ public static class PublishSnapshotNormalizer
|
|||||||
}
|
}
|
||||||
|
|
||||||
NormalizeKpiAreas(snapshot.PriceAreaInfos);
|
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)
|
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