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