diff --git a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs index 344f1ab..5cf568d 100644 --- a/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs +++ b/src/Oms.Application/Services/Channels/SalesProductService.Helpers.cs @@ -388,10 +388,11 @@ public partial class SalesProductService }; /// - /// 保险/偏远费等附加费用项的计费条件校验。 + /// 保险/偏远费等附加费用项的计费条件校验;同一提交批次内费用项不可重复。 /// private static void ValidateBillingPriceItemRules(IReadOnlyList prices) { + EnsureUniqueCostItemsInBatch(prices.Select(x => x.CostItem)); foreach (var price in prices) { var costItem = price.CostItem?.Trim() ?? string.Empty; @@ -417,6 +418,44 @@ public partial class SalesProductService } } + /// + /// 同一销售产品下每种费用项只能有一条卖价配置。 + /// + private async Task EnsureCostItemUniqueAsync(int salesProductId, string? costItem, int? excludePriceId) + { + var trimmed = costItem?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(trimmed)) + { + throw new Fake.Application.BusinessException("费用项不能为空"); + } + + var existing = await SalesProductRepository.GetPricesAsync(salesProductId); + var duplicated = existing.Any(p => + (!excludePriceId.HasValue || p.Id != excludePriceId.Value) + && string.Equals(p.CostItem?.Trim(), trimmed, StringComparison.OrdinalIgnoreCase)); + if (duplicated) + { + throw new Fake.Application.BusinessException( + $"该产品已存在费用项「{trimmed}」,同一产品下每种费用项只能有一条"); + } + } + + private static void EnsureUniqueCostItemsInBatch(IEnumerable costItems) + { + var duplicates = costItems + .Select(x => x?.Trim() ?? string.Empty) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .GroupBy(x => x, StringComparer.OrdinalIgnoreCase) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + if (duplicates.Count > 0) + { + throw new Fake.Application.BusinessException( + $"同一产品下费用项不能重复:{string.Join("、", duplicates)}"); + } + } + private static string? NormalizeAdditionalServices(string? raw) { if (string.IsNullOrWhiteSpace(raw)) diff --git a/src/Oms.Application/Services/Channels/SalesProductService.cs b/src/Oms.Application/Services/Channels/SalesProductService.cs index 3b86f15..29ddaf2 100644 --- a/src/Oms.Application/Services/Channels/SalesProductService.cs +++ b/src/Oms.Application/Services/Channels/SalesProductService.cs @@ -159,6 +159,7 @@ public partial class SalesProductService : ApplicationService { await GetProductOrThrowAsync(input.SalesProductId); ValidateBillingPriceItemRules([input]); + await EnsureCostItemUniqueAsync(input.SalesProductId, input.CostItem, excludePriceId: null); await SalesProductRepository.InsertPriceAsync( new SalesProductPrice { @@ -190,6 +191,7 @@ public partial class SalesProductService : ApplicationService } ValidateBillingPriceItemRules([input]); + await EnsureCostItemUniqueAsync(input.SalesProductId, input.CostItem, excludePriceId: price.Id); price.SerialNumber = input.SerialNumber; price.Status = input.Status; price.PriceName = input.PriceName; diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs index 2a00598..0f8c182 100644 --- a/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs +++ b/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs @@ -329,10 +329,11 @@ public partial class ServiceChannelService } /// - /// 买价偏远费等附加费用项的计费条件校验。 + /// 买价偏远费等附加费用项的计费条件校验;同一提交批次内费用项不可重复。 /// private static void ValidateBillingPriceItemRules(IReadOnlyList prices) { + EnsureUniqueCostItemsInBatch(prices.Select(x => x.CostItem)); foreach (var price in prices) { var costItem = price.CostItem?.Trim() ?? string.Empty; @@ -349,6 +350,44 @@ public partial class ServiceChannelService } } + /// + /// 同一服务渠道下每种费用项只能有一条买价配置。 + /// + private async Task EnsureCostItemUniqueAsync(int serviceChannelId, string? costItem, int? excludePriceId) + { + var trimmed = costItem?.Trim() ?? string.Empty; + if (string.IsNullOrWhiteSpace(trimmed)) + { + throw new Fake.Application.BusinessException("费用项不能为空"); + } + + var existing = await ServiceChannelRepository.GetPricesAsync(serviceChannelId); + var duplicated = existing.Any(p => + (!excludePriceId.HasValue || p.Id != excludePriceId.Value) + && string.Equals(p.CostItem?.Trim(), trimmed, StringComparison.OrdinalIgnoreCase)); + if (duplicated) + { + throw new Fake.Application.BusinessException( + $"该渠道已存在费用项「{trimmed}」,同一渠道下每种费用项只能有一条"); + } + } + + private static void EnsureUniqueCostItemsInBatch(IEnumerable costItems) + { + var duplicates = costItems + .Select(x => x?.Trim() ?? string.Empty) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .GroupBy(x => x, StringComparer.OrdinalIgnoreCase) + .Where(g => g.Count() > 1) + .Select(g => g.Key) + .ToList(); + if (duplicates.Count > 0) + { + throw new Fake.Application.BusinessException( + $"同一渠道下费用项不能重复:{string.Join("、", duplicates)}"); + } + } + private static ServiceChannelGroupListOutput MapGroupListOutput(ServiceChannelGroup entity) { return new ServiceChannelGroupListOutput diff --git a/src/Oms.Application/Services/Channels/ServiceChannelService.cs b/src/Oms.Application/Services/Channels/ServiceChannelService.cs index 7188468..137cd20 100644 --- a/src/Oms.Application/Services/Channels/ServiceChannelService.cs +++ b/src/Oms.Application/Services/Channels/ServiceChannelService.cs @@ -223,6 +223,7 @@ public partial class ServiceChannelService : ApplicationService { await GetEntityOrThrowAsync(input.ServiceChannelId); ValidateBillingPriceItemRules([input]); + await EnsureCostItemUniqueAsync(input.ServiceChannelId, input.CostItem, excludePriceId: null); await ServiceChannelRepository.InsertPriceAsync( new ServiceChannelPrice { @@ -254,6 +255,7 @@ public partial class ServiceChannelService : ApplicationService } ValidateBillingPriceItemRules([input]); + await EnsureCostItemUniqueAsync(input.ServiceChannelId, input.CostItem, excludePriceId: price.Id); price.SerialNumber = input.SerialNumber; price.Status = input.Status; price.PriceName = input.PriceName;