feat: 同一产品/渠道下卖价买价费用项唯一,禁止重复新增

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-10-05 11:23:38 +08:00
parent 1b6e91b09d
commit 79099776a5
4 changed files with 84 additions and 2 deletions

View File

@ -388,10 +388,11 @@ public partial class SalesProductService
};
/// <summary>
/// 保险/偏远费等附加费用项的计费条件校验。
/// 保险/偏远费等附加费用项的计费条件校验;同一提交批次内费用项不可重复。
/// </summary>
private static void ValidateBillingPriceItemRules(IReadOnlyList<SalesProductPriceSaveInput> 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
}
}
/// <summary>
/// 同一销售产品下每种费用项只能有一条卖价配置。
/// </summary>
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<string?> 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))

View File

@ -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;

View File

@ -329,10 +329,11 @@ public partial class ServiceChannelService
}
/// <summary>
/// 买价偏远费等附加费用项的计费条件校验。
/// 买价偏远费等附加费用项的计费条件校验;同一提交批次内费用项不可重复。
/// </summary>
private static void ValidateBillingPriceItemRules(IReadOnlyList<ServiceChannelPriceSaveInput> 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
}
}
/// <summary>
/// 同一服务渠道下每种费用项只能有一条买价配置。
/// </summary>
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<string?> 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

View File

@ -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;