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); } }