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 ServiceChannelService : ApplicationService
{
private const string PriceOperatorType = "price";
private const string RulesOperatorType = "rules";
private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService();
private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService();
///
/// 查询服务渠道列表。
///
/// 入参:服务渠道列表查询条件。
/// 服务渠道分页结果,包含列表数据和总记录数。
///
/// 核心业务逻辑:
/// 1. 根据查询条件构造筛选规则。
/// 2. 查询目标数据并补齐关联展示字段。
/// 3. 按接口约定返回列表结果。
///
[HttpGet("/channel/service-channel/list")]
public async Task> GetListAsync([FromQuery] ServiceChannelListInput input)
{
var predicate = BuildListPredicate(input);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await ServiceChannelRepository.GetPagedListAsync(
predicate,
pageIndex,
pageSize,
new Dictionary { [nameof(ServiceChannel.UpdateTime)] = false, [nameof(ServiceChannel.Id)] = false });
var total = await ServiceChannelRepository.CountAsync(predicate);
var groupMap = await GetGroupMapAsync();
return new OmsPageResult
{
Items = entities.Select(entity => MapListOutput(entity, groupMap)).ToList(),
Total = total
};
}
///
/// 查询服务渠道详情。
///
/// 入参:目标主键。
/// 服务渠道详情。
///
/// 核心业务逻辑:
/// 1. 根据标识查询目标对象。
/// 2. 目标不存在时抛出业务异常。
/// 3. 映射并返回详情结果。
///
[HttpGet("/channel/service-channel/detail")]
public async Task GetDetailAsync([FromQuery] int id)
{
var entity = await GetEntityOrThrowAsync(id);
return MapDetailOutput(entity, await GetGroupMapAsync());
}
///
/// 新增服务渠道。
///
/// 入参:服务渠道保存参数。
/// 无返回体,仅完成新增服务渠道。
///
/// 核心业务逻辑:
/// 1. 校验入参与关联对象、唯一性等前置条件。
/// 2. 组装目标实体及相关明细数据。
/// 3. 写入审计字段并持久化结果。
///
[HttpPost("/channel/service-channel/create")]
public async Task CreateAsync(ServiceChannelSaveInput input)
{
await ValidateChannelCodeAsync(input.ChannelCode, null);
var entity = new ServiceChannel();
ApplyInput(entity, input);
await ServiceChannelRepository.InsertAsync(entity);
}
///
/// 更新服务渠道。
///
/// 入参:服务渠道保存参数。
/// 无返回体,仅完成更新服务渠道。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 按入参更新主体数据和关联配置。
/// 3. 写入更新审计字段并保存结果。
///
[HttpPost("/channel/service-channel/update")]
public async Task UpdateAsync(ServiceChannelSaveInput input)
{
var entity = await GetEntityOrThrowAsync(input.Id);
await ValidateChannelCodeAsync(input.ChannelCode, entity.Id);
ApplyInput(entity, input);
await ServiceChannelRepository.UpdateAsync(entity);
}
///
/// 删除服务渠道。
///
/// 入参:目标主键。
/// 无返回体,仅完成删除服务渠道。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 执行软删除或删除关联明细。
/// 3. 持久化删除结果。
///
[HttpPost("/channel/service-channel/delete")]
public async Task DeleteAsync(IdInput input)
{
var entity = await GetEntityOrThrowAsync(input.Id);
await ServiceChannelRepository.DeleteAsync(entity);
}
///
/// 查询服务渠道分组列表。
///
/// 入参:分组名称、应用 ID、API 服务商 ID 等筛选条件。
/// 服务渠道分组列表,供下拉与左侧树筛选使用。
///
/// 核心业务逻辑:
/// 1. 按 keyword、appId、apiServiceProviderId 构造筛选条件。
/// 2. 查询未删除分组并按 id 倒序返回。
/// 3. 无数据时返回空数组。
///
[HttpGet("/channel/service-channel/group/list")]
public async Task> GetGroupListAsync([FromQuery] ServiceChannelGroupListInput input)
{
var predicate = BuildGroupListPredicate(input);
var entities = await ServiceChannelGroupRepository.GetListAsync(
predicate,
new Dictionary { [nameof(ServiceChannelGroup.Id)] = false });
return entities.Select(MapGroupListOutput).ToList();
}
///
/// 新增服务渠道分组。
///
/// 入参:服务渠道分组保存参数。
/// 无返回体,仅完成新增服务渠道分组。
///
/// 核心业务逻辑:
/// 1. 校验分组名称、应用ID、API服务商ID等基础字段。
/// 2. 检查分组名称唯一性,避免重复配置。
/// 3. 保存分组主数据供服务渠道绑定。
///
[HttpPost("/channel/service-channel/group/create")]
public async Task CreateGroupAsync(ServiceChannelGroupSaveInput input)
{
await ValidateGroupInputAsync(input, null);
await ServiceChannelGroupRepository.InsertAsync(new ServiceChannelGroup
{
Name = input.Name.Trim(),
AppId = input.AppId,
ApiServiceProviderId = input.ApiServiceProviderId
});
}
///
/// 更新服务渠道分组。
///
/// 入参:服务渠道分组保存参数。
/// 无返回体,仅完成更新服务渠道分组。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标分组是否存在。
/// 2. 检查名称唯一性并更新分组主数据。
/// 3. 保留既有渠道关联,只更新分组属性。
///
[HttpPost("/channel/service-channel/group/update")]
public async Task UpdateGroupAsync(ServiceChannelGroupSaveInput input)
{
var group = await GetGroupOrThrowAsync(input.Id);
await ValidateGroupInputAsync(input, group.Id);
group.Name = input.Name.Trim();
group.AppId = input.AppId;
group.ApiServiceProviderId = input.ApiServiceProviderId;
await ServiceChannelGroupRepository.UpdateAsync(group);
}
///
/// 删除服务渠道分组。
///
/// 入参:目标分组主键。
/// 无返回体,仅完成删除服务渠道分组。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标分组是否存在。
/// 2. 检查分组下是否仍有关联渠道。
/// 3. 无关联时执行删除,保留框架软删语义。
///
[HttpPost("/channel/service-channel/group/delete")]
public async Task DeleteGroupAsync(IdInput input)
{
var group = await GetGroupOrThrowAsync(input.Id);
if (await ServiceChannelRepository.AnyAsync(x => x.GroupId == group.Id))
{
throw new BusinessException("当前分组下仍有关联渠道,无法删除");
}
await ServiceChannelGroupRepository.DeleteAsync(group);
}
///
/// 保存服务渠道配置。
///
/// 入参:服务渠道保存参数。
/// 无返回体,仅完成保存服务渠道配置。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 按入参更新主体数据和关联配置。
/// 3. 写入更新审计字段并保存结果。
///
[ApiExplorerSettings(IgnoreApi = true)]
[HttpPost("/channel/service-channel/config/save")]
public async Task SaveConfigAsync(ServiceChannelSaveInput input)
{
var entity = await GetEntityOrThrowAsync(input.Id);
await ValidateChannelCodeAsync(input.ChannelCode, entity.Id);
ApplyInput(entity, input);
await ServiceChannelRepository.UpdateAsync(entity);
}
///
/// 查询服务渠道价格项列表。
///
/// 入参:serviceChannelId。
/// 服务渠道价格项列表结果。
///
/// 核心业务逻辑:
/// 1. 根据查询条件构造筛选规则。
/// 2. 查询目标数据并补齐关联展示字段。
/// 3. 按接口约定返回列表结果。
///
[HttpGet("/channel/service-channel/price/list")]
public async Task> GetPriceListAsync([FromQuery] int serviceChannelId)
{
await GetEntityOrThrowAsync(serviceChannelId);
return new OmsItemsResult
{
Items = await GetPriceOutputsAsync(serviceChannelId)
};
}
///
/// 新增服务渠道价格项。
///
/// 入参:服务渠道价格项保存参数。
/// 无返回体,仅完成新增服务渠道价格项。
///
/// 核心业务逻辑:
/// 1. 校验入参与关联对象、唯一性等前置条件。
/// 2. 组装目标实体及相关明细数据。
/// 3. 写入审计字段并持久化结果。
///
[HttpPost("/channel/service-channel/price/create")]
public async Task CreatePriceAsync(ServiceChannelPriceSaveInput input)
{
await GetEntityOrThrowAsync(input.ServiceChannelId);
await ServiceChannelRepository.InsertPriceAsync(
new ServiceChannelPrice
{
ServiceChannelId = input.ServiceChannelId,
SerialNumber = input.SerialNumber,
Status = input.Status,
PriceName = input.PriceName,
CostItem = input.CostItem,
CostUnit = input.CostUnit,
Currency = input.Currency,
ChargingTime = input.ChargingTime,
ChargingCondition = input.ChargingCondition
},
BuildPriceFactors(input.Factors),
BuildPriceDetails(input.Details));
}
///
/// 更新服务渠道价格项。
///
/// 入参:服务渠道价格项保存参数。
/// 无返回体,仅完成更新服务渠道价格项。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 按入参更新主体数据和关联配置。
/// 3. 写入更新审计字段并保存结果。
///
[HttpPost("/channel/service-channel/price/update")]
public async Task UpdatePriceAsync(ServiceChannelPriceSaveInput input)
{
var price = await GetPriceOrThrowAsync(input.Id);
if (price.ServiceChannelId != input.ServiceChannelId)
{
throw new BusinessException("买价项归属不匹配");
}
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.ChargingTime = input.ChargingTime;
price.ChargingCondition = input.ChargingCondition;
await ServiceChannelRepository.UpdatePriceAsync(
price,
BuildPriceFactors(input.Factors),
BuildPriceDetails(input.Details));
}
///
/// 删除服务渠道价格项。
///
/// 入参:目标主键。
/// 无返回体,仅完成删除服务渠道价格项。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 执行软删除或删除关联明细。
/// 3. 持久化删除结果。
///
[HttpPost("/channel/service-channel/price/delete")]
public async Task DeletePriceAsync(IdInput input)
{
await GetPriceOrThrowAsync(input.Id);
await ServiceChannelRepository.DeletePriceAsync(input.Id);
}
///
/// 切换服务渠道价格项状态。
///
/// 入参:目标主键和状态值。
/// 无返回体,仅完成切换服务渠道价格项状态。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 更新启停状态。
/// 3. 保存状态变更结果。
///
[HttpPost("/channel/service-channel/price/toggleStatus")]
public async Task TogglePriceStatusAsync(ToggleStatusInput input)
{
await GetPriceOrThrowAsync(input.Id);
await ServiceChannelRepository.UpdatePriceStatusAsync(input.Id, input.Status);
}
///
/// 批量保存服务渠道价格项。
///
/// 入参:服务渠道价格项批量保存参数。
/// 无返回体,仅完成批量保存服务渠道价格项。
///
/// 核心业务逻辑:
/// 1. 查询并校验目标对象。
/// 2. 按入参更新主体数据和关联配置。
/// 3. 写入更新审计字段并保存结果。
///
[HttpPost("/channel/service-channel/price/save")]
public async Task SavePriceAsync(ServiceChannelPriceSaveAllInput input)
{
await GetEntityOrThrowAsync(input.ServiceChannelId);
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 ServiceChannelPrice
{
Id = currentTempId,
ServiceChannelId = input.ServiceChannelId,
SerialNumber = priceInput.SerialNumber,
Status = priceInput.Status,
PriceName = priceInput.PriceName,
CostItem = priceInput.CostItem,
CostUnit = priceInput.CostUnit,
Currency = priceInput.Currency,
ChargingTime = priceInput.ChargingTime,
ChargingCondition = priceInput.ChargingCondition
});
factors.AddRange(BuildPriceFactors(priceInput.Factors, currentTempId));
details.AddRange(BuildPriceDetails(priceInput.Details, currentTempId));
}
await ServiceChannelRepository.ReplacePricesAsync(input.ServiceChannelId, prices, factors, details);
if (input.Publish)
{
await CreatePublishRecordAsync(input.ServiceChannelId, PriceOperatorType, "保存并发布买价");
}
}
///
/// 查询服务渠道发布价格列表。
///
/// 入参:服务渠道发布列表查询条件。
/// 服务渠道发布价格分页结果,包含列表数据和总记录数。
///
/// 核心业务逻辑:
/// 1. 根据查询条件构造筛选规则。
/// 2. 查询目标数据并补齐关联展示字段。
/// 3. 按接口约定返回列表结果。
///
[HttpGet("/channel/service-channel/publish/price/list")]
public Task> GetPricePublishListAsync([FromQuery] ServiceChannelPublishListInput input)
{
return GetPublishListAsync(input, PriceOperatorType);
}
///
/// 查询服务渠道发布规则列表。
///
/// 入参:服务渠道发布列表查询条件。
/// 服务渠道发布规则分页结果,包含列表数据和总记录数。
///
/// 核心业务逻辑:
/// 1. 根据查询条件构造筛选规则。
/// 2. 查询目标数据并补齐关联展示字段。
/// 3. 按接口约定返回列表结果。
///
[HttpGet("/channel/service-channel/publish/rules/list")]
public Task> GetRulesPublishListAsync([FromQuery] ServiceChannelPublishListInput input)
{
return GetPublishListAsync(input, RulesOperatorType);
}
///
/// 执行服务渠道发布。
///
/// 入参:服务渠道发布参数。
/// 无返回体,仅完成服务渠道发布处理。
///
/// 核心业务逻辑:
/// 1. 校验目标对象和发布前提条件。
/// 2. 生成、切换或回退发布版本数据。
/// 3. 保存发布结果并返回处理信息。
///
[HttpPost("/channel/service-channel/publish")]
public async Task PublishAsync(ServiceChannelPublishInput input)
{
await CreatePublishRecordAsync(input.ServiceChannelId, NormalizeOperatorType(input.OperatorType), input.Description);
}
///
/// 回滚服务渠道发布版本。
///
/// 入参:服务渠道发布回滚参数。
/// 无返回体,仅完成回滚服务渠道发布版本。
///
/// 核心业务逻辑:
/// 1. 校验目标对象和发布前提条件。
/// 2. 生成、切换或回退发布版本数据。
/// 3. 保存发布结果并返回处理信息。
///
[HttpPost("/channel/service-channel/publish/rollback")]
public Task RollbackAsync(ServiceChannelRollbackInput input)
{
return RollbackCoreAsync(input);
}
private static Expression> BuildListPredicate(ServiceChannelListInput input)
{
var keyword = input.Keyword?.Trim();
var hasGroupFilter = input.GroupId.HasValue;
var groupIdValue = input.GroupId.GetValueOrDefault();
var hasIsActiveFilter = input.IsActive.HasValue;
var isActiveValue = input.IsActive.GetValueOrDefault();
return entity =>
(!hasGroupFilter || entity.GroupId == groupIdValue) &&
(string.IsNullOrWhiteSpace(keyword) || entity.ChannelCode.Contains(keyword) || entity.ChannelName.Contains(keyword)) &&
(!hasIsActiveFilter || entity.IsActive == isActiveValue);
}
private static Expression> BuildGroupListPredicate(ServiceChannelGroupListInput input)
{
var keyword = input.Keyword?.Trim();
var hasAppId = input.AppId.HasValue;
var appId = input.AppId.GetValueOrDefault();
var hasApiServiceProviderId = input.ApiServiceProviderId.HasValue;
var apiServiceProviderId = input.ApiServiceProviderId.GetValueOrDefault();
return entity =>
(string.IsNullOrWhiteSpace(keyword) || entity.Name.Contains(keyword)) &&
(!hasAppId || entity.AppId == appId) &&
(!hasApiServiceProviderId || entity.ApiServiceProviderId == apiServiceProviderId);
}
}