using System.Linq.Expressions; using Fake.Application; using Microsoft.AspNetCore.Mvc; using Oms.Application.Dtos.Channels; using Oms.Application.Dtos.Shared; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Entities; using Oms.Domain.ChannelsAggregate.Repositories; namespace Oms.Application.Services.Channels; /// /// 销售产品 API 服务。 /// [ApiExplorerSettings(GroupName = "OMS-Channel")] public partial class SalesProductService : ApplicationService { private const string PriceOperatorType = "price"; private const string RulesOperatorType = "rules"; private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService(); private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService(); private IKpiAreaRepository KpiAreaRepository => LazyServiceProvider.GetRequiredService(); private IWeightRuleRepository WeightRuleRepository => LazyServiceProvider.GetRequiredService(); private IWeightRoundingRepository WeightRoundingRepository => LazyServiceProvider.GetRequiredService(); private AllocationProviderSnapshotEnricher AllocationProviderSnapshotEnricher => LazyServiceProvider.GetRequiredService(); private OmsRbacUserDisplayNameResolver UserDisplayNameResolver => LazyServiceProvider.GetRequiredService(); /// /// 查询销售产品列表。 /// /// 入参:销售产品列表查询条件。 /// 销售产品分页结果,包含列表数据和总记录数。 [HttpGet("/channel/sales-product/list")] public async Task> GetListAsync([FromQuery] SalesProductListInput input) { var predicate = BuildListPredicate(input); var pageIndex = OmsDocHelper.NormalizePage(input.Page); var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize); var entities = await SalesProductRepository.GetPagedListAsync( predicate, pageIndex, pageSize, new Dictionary { [nameof(SalesProduct.UpdateTime)] = false, [nameof(SalesProduct.Id)] = false }); var total = await SalesProductRepository.CountAsync(predicate); return new OmsPageResult { Items = entities.Select(MapListOutput).ToList(), Total = total }; } /// /// 查询销售产品详情。 /// /// 入参:目标主键。 /// 销售产品详情。 [HttpGet("/channel/sales-product/detail")] public async Task GetDetailAsync([FromQuery] int id) { var entity = await GetProductOrThrowAsync(id); var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync( [entity.CreateUserId, entity.UpdateUserId]); return MapDetailOutput(entity, auditUserNameMap); } /// /// 新增销售产品。 /// /// 入参:销售产品保存参数。 /// 无返回体,仅完成新增销售产品。 [HttpPost("/channel/sales-product/create")] public async Task CreateAsync(SalesProductSaveInput input) { await ValidateProductCodeAsync(input.ProductCode, null); var entity = new SalesProduct(); ApplyProductInput(entity, input); await SalesProductRepository.InsertAsync(entity); } /// /// 更新销售产品。 /// /// 入参:销售产品保存参数。 /// 无返回体,仅完成更新销售产品。 [HttpPost("/channel/sales-product/update")] public async Task UpdateAsync(SalesProductSaveInput input) { var entity = await GetProductOrThrowAsync(input.Id); await ValidateProductCodeAsync(input.ProductCode, entity.Id); ApplyProductInput(entity, input); await SalesProductRepository.UpdateAsync(entity); } /// /// 删除销售产品。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除销售产品。 [HttpPost("/channel/sales-product/delete")] public async Task DeleteAsync(IdInput input) { var entity = await GetProductOrThrowAsync(input.Id); await SalesProductRepository.DeleteAsync(entity); } /// /// 保存销售产品配置。 /// /// 入参:销售产品保存参数。 /// 无返回体,仅完成保存销售产品配置。 [ApiExplorerSettings(IgnoreApi = true)] [HttpPost("/channel/sales-product/config/save")] public async Task SaveConfigAsync(SalesProductSaveInput input) { var entity = await GetProductOrThrowAsync(input.Id); await ValidateProductCodeAsync(input.ProductCode, entity.Id); ApplyProductInput(entity, input); await SalesProductRepository.UpdateAsync(entity); } /// /// 保存销售产品对客说明。 /// /// 入参:对客说明 Markdown。 /// 无返回体,仅完成保存对客说明。 [HttpPost("/channel/sales-product/customer-guide/save")] public async Task SaveCustomerGuideAsync(SalesProductCustomerGuideSaveInput input) { var entity = await GetProductOrThrowAsync(input.SalesProductId); entity.CustomerGuideMarkdown = input.CustomerGuideMarkdown; await SalesProductRepository.UpdateAsync(entity); } /// /// 查询销售产品价格项列表。 /// /// 入参:销售产品主键。 /// 销售产品价格项列表结果。 [HttpGet("/channel/sales-product/price/list")] public async Task> GetPriceListAsync([FromQuery] int salesProductId) { await GetProductOrThrowAsync(salesProductId); return new OmsItemsResult { Items = await GetPriceOutputsAsync(salesProductId) }; } /// /// 新增销售产品价格项。 /// /// 入参:销售产品价格项保存参数。 /// 无返回体,仅完成新增销售产品价格项。 [HttpPost("/channel/sales-product/price/create")] public async Task CreatePriceAsync(SalesProductPriceSaveInput input) { await GetProductOrThrowAsync(input.SalesProductId); ValidateBillingPriceItemRules([input]); await SalesProductRepository.InsertPriceAsync( new SalesProductPrice { SalesProductId = input.SalesProductId, SerialNumber = input.SerialNumber, Status = input.Status, PriceName = input.PriceName, CostItem = input.CostItem, CostUnit = input.CostUnit, Currency = input.Currency, ChargingCondition = input.ChargingCondition }, BuildSalesProductPriceFactors(input.Factors), BuildSalesProductPriceDetails(input.Details)); } /// /// 更新销售产品价格项。 /// /// 入参:销售产品价格项保存参数。 /// 无返回体,仅完成更新销售产品价格项。 [HttpPost("/channel/sales-product/price/update")] public async Task UpdatePriceAsync(SalesProductPriceSaveInput input) { var price = await GetPriceOrThrowAsync(input.Id); if (price.SalesProductId != input.SalesProductId) { throw new BusinessException("价格项归属不匹配"); } ValidateBillingPriceItemRules([input]); price.SerialNumber = input.SerialNumber; price.Status = input.Status; price.PriceName = input.PriceName; price.CostItem = input.CostItem; price.CostUnit = input.CostUnit; price.Currency = input.Currency; price.ChargingCondition = input.ChargingCondition; await SalesProductRepository.UpdatePriceAsync( price, BuildSalesProductPriceFactors(input.Factors), BuildSalesProductPriceDetails(input.Details)); } /// /// 删除销售产品价格项。 /// /// 入参:目标主键。 /// 无返回体,仅完成删除销售产品价格项。 [HttpPost("/channel/sales-product/price/delete")] public async Task DeletePriceAsync(IdInput input) { await GetPriceOrThrowAsync(input.Id); await SalesProductRepository.DeletePriceAsync(input.Id); } /// /// 切换销售产品价格项状态。 /// /// 入参:目标主键和状态值。 /// 无返回体,仅完成切换销售产品价格项状态。 [HttpPost("/channel/sales-product/price/toggleStatus")] public async Task TogglePriceStatusAsync(ToggleStatusInput input) { await GetPriceOrThrowAsync(input.Id); await SalesProductRepository.UpdatePriceStatusAsync(input.Id, input.Status); } /// /// 批量保存销售产品价格项。 /// /// 入参:销售产品价格项批量保存参数。 /// 无返回体,仅完成批量保存销售产品价格项。 [HttpPost("/channel/sales-product/price/save")] public async Task SavePriceAsync(SalesProductPriceSaveAllInput input) { await GetProductOrThrowAsync(input.SalesProductId); ValidateBillingPriceItemRules(input.Prices); await PriceAreaSnapshotHelper.EnsureSingleResolvedPriceAreaAsync( BuildPriceSnapshotFromSaveInputs(input.SalesProductId, input.Prices), KpiAreaRepository); var prices = new List(); var factors = new List(); var details = new List(); var tempId = -1; // 为每棵价格子树分配临时负数 Id,仓储层再据此回填真实主键。 foreach (var priceInput in input.Prices.OrderBy(x => x.SerialNumber)) { var currentTempId = tempId--; prices.Add(new SalesProductPrice { Id = currentTempId, SalesProductId = input.SalesProductId, SerialNumber = priceInput.SerialNumber, Status = priceInput.Status, PriceName = priceInput.PriceName, CostItem = priceInput.CostItem, CostUnit = priceInput.CostUnit, Currency = priceInput.Currency, ChargingCondition = priceInput.ChargingCondition }); factors.AddRange(BuildSalesProductPriceFactors(priceInput.Factors, currentTempId)); details.AddRange(BuildSalesProductPriceDetails(priceInput.Details, currentTempId)); } await SalesProductRepository.ReplacePricesAsync(input.SalesProductId, prices, factors, details); if (input.Publish) { if (string.IsNullOrWhiteSpace(input.AiAnalysisReport)) { throw new Fake.Application.BusinessException("价格发布前须完成 AI 风险分析并保存报告"); } await CreatePublishRecordAsync( input.SalesProductId, PriceOperatorType, "保存并发布价格", input.AiAnalysisReport); } } /// /// 查询销售产品分配配置。 /// /// 入参:销售产品主键。 /// 销售产品分配配置。 [HttpGet("/channel/sales-product/allocation/get")] public Task GetAllocationAsync([FromQuery] int salesProductId) { return GetAllocationCoreAsync(salesProductId); } /// /// 保存销售产品分配配置。 /// /// 入参:销售产品分配配置保存参数。 /// 无返回体,仅完成保存销售产品分配配置。 [HttpPost("/channel/sales-product/allocation/save")] public async Task SaveAllocationAsync(SalesProductAllocationSaveInput input) { var product = await GetProductOrThrowAsync(input.SalesProductId); product.Scheme = input.Scheme; await SalesProductRepository.UpdateAsync(product); var providers = input.Providers.Select(provider => new SalesProductAllocationProvider { SalesProductId = input.SalesProductId, SerialNumber = provider.SerialNumber, ProviderName = provider.ProviderName, ServiceChannelId = provider.ServiceChannelId, Weight = provider.Weight, CustomerIds = OmsDocHelper.SerializeJson(provider.CustomerIds), MatchCondition = provider.MatchCondition }).ToList(); await SalesProductRepository.ReplaceAllocationProvidersAsync(input.SalesProductId, providers); if (input.Publish) { await CreatePublishRecordAsync(input.SalesProductId, RulesOperatorType, "保存并发布规则"); } } /// /// 查询销售产品发布价格列表。 /// /// 入参:销售产品发布列表查询条件。 /// 销售产品发布价格分页结果,包含列表数据和总记录数。 [HttpGet("/channel/sales-product/publish/price/list")] public Task> GetPricePublishListAsync([FromQuery] SalesProductPublishListInput input) { return GetPublishListAsync(input, PriceOperatorType); } /// /// 查询销售产品发布规则列表。 /// /// 入参:销售产品发布列表查询条件。 /// 销售产品发布规则分页结果,包含列表数据和总记录数。 [HttpGet("/channel/sales-product/publish/rules/list")] public Task> GetRulesPublishListAsync([FromQuery] SalesProductPublishListInput input) { 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); object snapshot = normalized == PriceOperatorType ? await BuildPricePublishSnapshotAsync(salesProductId) : await BuildRulesPublishSnapshotAsync(salesProductId); return OmsDocHelper.ToSnapshotApiContent(snapshot)!; } /// /// 执行销售产品发布。 /// /// 入参:销售产品发布参数。 /// 无返回体,仅完成销售产品发布处理。 [HttpPost("/channel/sales-product/publish")] public async Task PublishAsync(SalesProductPublishInput input) { var operatorType = NormalizeOperatorType(input.OperatorType); if (operatorType == PriceOperatorType && string.IsNullOrWhiteSpace(input.AiAnalysisReport)) { throw new Fake.Application.BusinessException("价格发布前须完成 AI 风险分析并保存报告"); } await CreatePublishRecordAsync( input.SalesProductId, operatorType, input.Description, input.AiAnalysisReport); } /// /// 回滚销售产品发布版本。 /// /// 入参:销售产品发布回滚参数。 /// 无返回体,仅完成回滚销售产品发布版本。 [HttpPost("/channel/sales-product/publish/rollback")] public Task RollbackAsync(SalesProductRollbackInput input) { return RollbackCoreAsync(input); } private static Expression> BuildListPredicate(SalesProductListInput input) { var productGroup = input.ProductGroup?.Trim(); var keyword = input.Keyword?.Trim(); var hasIsActiveFilter = input.IsActive.HasValue; var isActiveValue = input.IsActive.GetValueOrDefault(); return entity => (string.IsNullOrWhiteSpace(productGroup) || entity.ProductGroup == productGroup) && (string.IsNullOrWhiteSpace(keyword) || entity.ProductCode.Contains(keyword) || entity.ProductName.Contains(keyword)) && (!hasIsActiveFilter || entity.IsActive == isActiveValue); } }