From d8a98fbbc89cff70ca2225be91434b0fd0ca6711 Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Fri, 26 Jun 2026 13:48:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=84=E5=88=99=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E9=A2=84=E8=A7=88=E6=8E=A5=E5=8F=A3=E5=B9=B6=E6=8A=BD=E5=8F=96?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E7=BB=84=E8=A3=85=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 GET publish/preview 返回含黑名单/偏远/计重/进位明细的完整 rules 快照,便于核对 Content。 Co-authored-by: Cursor --- .../Channels/SalesProductService.Helpers.cs | 104 ++++++++++-------- .../Services/Channels/SalesProductService.cs | 12 ++ .../RulesSnapshotHelperPublishTests.cs | 88 +++++++++++++++ 3 files changed, 159 insertions(+), 45 deletions(-) create mode 100644 src/Oms.Tests/RulesSnapshotHelperPublishTests.cs diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs index 948094b..29d8f24 100644 --- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs +++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs @@ -155,56 +155,70 @@ public partial class SalesProductService }; } + /// + /// 组装规则发布快照(不落库),含黑名单/偏远/计重/进位明细。 + /// + private async Task BuildRulesPublishSnapshotAsync(int salesProductId) + { + var product = await GetProductOrThrowAsync(salesProductId); + var rulesSnapshot = new SalesProductRulesPublishSnapshot + { + Product = MapProductToRulesSaveInput(product), + Providers = (await GetAllocationCoreAsync(salesProductId)).Providers + }; + await RulesSnapshotHelper.ApplyRulesToSnapshotAsync( + rulesSnapshot, + product, + KpiAreaRepository, + WeightRuleRepository, + WeightRoundingRepository); + return rulesSnapshot; + } + + /// + /// 组装价格发布快照(不落库)。 + /// + private async Task BuildPricePublishSnapshotAsync(int salesProductId) + { + var product = await GetProductOrThrowAsync(salesProductId); + var priceSnapshot = new SalesProductPricePublishSnapshot + { + SalesProductId = salesProductId, + Prices = await GetPriceOutputsAsync(salesProductId) + }; + await PriceAreaSnapshotHelper.ApplyPriceAreaToSnapshotAsync(priceSnapshot, product, KpiAreaRepository); + return priceSnapshot; + } + + private static SalesProductSaveInput MapProductToRulesSaveInput(SalesProduct product) => + new() + { + Id = product.Id, + ProductCode = product.ProductCode, + ProductName = product.ProductName, + ProductGroup = product.ProductGroup, + MinTimeliness = product.MinTimeliness, + MaxTimeliness = product.MaxTimeliness, + CustomerWaybill = product.CustomerWaybill, + BlacklistAreaId = product.BlacklistAreaId, + RemoteAreaId = product.RemoteAreaId, + PriceAreaId = product.PriceAreaId, + WeightRuleId = product.WeightRuleId, + WeightRoundingId = product.WeightRoundingId, + ProductDescription = product.ProductDescription, + PreStorageScript = product.PreStorageScript, + IsActive = product.IsActive, + Scheme = product.Scheme + }; + /// /// 为当前配置生成发布快照。 /// private async Task CreatePublishRecordAsync(int salesProductId, string operatorType, string? description) { - var product = await GetProductOrThrowAsync(salesProductId); - object snapshot; - if (operatorType == PriceOperatorType) - { - var priceSnapshot = new SalesProductPricePublishSnapshot - { - SalesProductId = salesProductId, - Prices = await GetPriceOutputsAsync(salesProductId) - }; - await PriceAreaSnapshotHelper.ApplyPriceAreaToSnapshotAsync(priceSnapshot, product, KpiAreaRepository); - snapshot = priceSnapshot; - } - else - { - var rulesSnapshot = new SalesProductRulesPublishSnapshot - { - Product = new SalesProductSaveInput - { - Id = product.Id, - ProductCode = product.ProductCode, - ProductName = product.ProductName, - ProductGroup = product.ProductGroup, - MinTimeliness = product.MinTimeliness, - MaxTimeliness = product.MaxTimeliness, - CustomerWaybill = product.CustomerWaybill, - BlacklistAreaId = product.BlacklistAreaId, - RemoteAreaId = product.RemoteAreaId, - PriceAreaId = product.PriceAreaId, - WeightRuleId = product.WeightRuleId, - WeightRoundingId = product.WeightRoundingId, - ProductDescription = product.ProductDescription, - PreStorageScript = product.PreStorageScript, - IsActive = product.IsActive, - Scheme = product.Scheme - }, - Providers = (await GetAllocationCoreAsync(salesProductId)).Providers - }; - await RulesSnapshotHelper.ApplyRulesToSnapshotAsync( - rulesSnapshot, - product, - KpiAreaRepository, - WeightRuleRepository, - WeightRoundingRepository); - snapshot = rulesSnapshot; - } + object snapshot = operatorType == PriceOperatorType + ? await BuildPricePublishSnapshotAsync(salesProductId) + : await BuildRulesPublishSnapshotAsync(salesProductId); // Content 中保存完整快照,回滚时直接从快照恢复,避免依赖当前库内状态。 await SalesProductRepository.InsertPublishRecordAsync(new SalesProductPublishRecord diff --git a/src/Oms.Application/Services/Channels/SalesProductService.cs b/src/Oms.Application/Services/Channels/SalesProductService.cs index c55cfb5..c61690b 100644 --- a/src/Oms.Application/Services/Channels/SalesProductService.cs +++ b/src/Oms.Application/Services/Channels/SalesProductService.cs @@ -408,6 +408,18 @@ public partial class SalesProductService : ApplicationService return GetPublishListAsync(input, RulesOperatorType); } + /// + /// 预览即将发布的快照 JSON(不落库),用于核对黑名单/计重等是否已固化进 Content。 + /// + [HttpGet("/channel/sales-product/publish/preview")] + public async Task PreviewPublishSnapshotAsync([FromQuery] int salesProductId, [FromQuery] string operatorType) + { + var normalized = NormalizeOperatorType(operatorType); + return normalized == PriceOperatorType + ? await BuildPricePublishSnapshotAsync(salesProductId) + : await BuildRulesPublishSnapshotAsync(salesProductId); + } + /// /// 执行销售产品发布。 /// diff --git a/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs b/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs new file mode 100644 index 0000000..3ccdda0 --- /dev/null +++ b/src/Oms.Tests/RulesSnapshotHelperPublishTests.cs @@ -0,0 +1,88 @@ +using FluentAssertions; +using Moq; +using Oms.Application.Dtos.Orders; +using Oms.Application.Shared; +using Oms.Domain.ChannelsAggregate.Entities; +using Oms.Domain.ChannelsAggregate.Repositories; + +namespace Oms.Tests; + +public class RulesSnapshotHelperPublishTests +{ + [Fact] + public async Task ApplyRulesToSnapshotAsync_ShouldEmbedBlacklistRemoteWeightAndRounding() + { + var product = new SalesProduct + { + Id = 1, + ProductCode = "test01", + BlacklistAreaId = 10, + RemoteAreaId = 20, + WeightRuleId = 1, + WeightRoundingId = 2 + }; + + var kpiMock = new Mock(); + kpiMock.Setup(x => x.FirstOrDefaultAsync(It.IsAny>>(), default)) + .ReturnsAsync((System.Linq.Expressions.Expression> _, CancellationToken _) => + new KpiArea { Id = 10, Name = "黑名单区" }); + kpiMock.Setup(x => x.GetAllInfosByAreaIdAsync(10, default)) + .ReturnsAsync( + [ + new KpiAreaInfo { Id = 1, AreaId = 10, CountryCode = "US", LeftPostcode = "90000", RightPostcode = "90010" } + ]); + kpiMock.Setup(x => x.GetAllInfosByAreaIdAsync(20, default)) + .ReturnsAsync( + [ + new KpiAreaInfo { Id = 2, AreaId = 20, CountryCode = "US", LeftPostcode = "06000", RightPostcode = "06999" } + ]); + + var weightRuleMock = new Mock(); + weightRuleMock.Setup(x => x.FirstOrDefaultAsync(It.IsAny>>(), default)) + .ReturnsAsync(new WeightRule { Id = 1, Name = "标准计重" }); + weightRuleMock.Setup(x => x.GetAllInfosByRuleIdAsync(1, default)) + .ReturnsAsync( + [ + new WeightRuleInfo + { + Id = 1, + RuleId = 1, + Condition = "gw>vw", + BillableWeight = "gw", + VolumetricFactor = "6000" + } + ]); + + var roundingMock = new Mock(); + roundingMock.Setup(x => x.FirstOrDefaultAsync(It.IsAny>>(), default)) + .ReturnsAsync(new WeightRounding { Id = 2, Name = "标准进位" }); + roundingMock.Setup(x => x.GetAllInfosByRoundingIdAsync(2, default)) + .ReturnsAsync( + [ + new WeightRoundingInfo { Id = 1, RoundingId = 2, StartValue = 0, EndValue = 20, Public = "0.5" } + ]); + + var snapshot = new SalesProductRulesPublishSnapshotDto(); + await RulesSnapshotHelper.ApplyRulesToSnapshotAsync( + snapshot, + product, + kpiMock.Object, + weightRuleMock.Object, + roundingMock.Object); + + snapshot.BlacklistAreaId.Should().Be(10); + snapshot.BlacklistAreaInfos.Should().HaveCount(1); + snapshot.RemoteAreaId.Should().Be(20); + snapshot.RemoteAreaInfos.Should().HaveCount(1); + snapshot.WeightRuleId.Should().Be(1); + snapshot.WeightRuleInfos.Should().HaveCount(1); + snapshot.WeightRoundingId.Should().Be(2); + snapshot.WeightRoundingInfos.Should().HaveCount(1); + + var json = OmsDocHelper.SerializeJson(snapshot); + json.Should().Contain("BlacklistAreaInfos"); + json.Should().Contain("WeightRuleInfos"); + json.Should().Contain("WeightRoundingInfos"); + json.Should().Contain("RemoteAreaInfos"); + } +}