admin/src/Oms.Application/Services/Channels/ServiceChannelService.Helpers.cs

438 lines
17 KiB
C#

using Oms.Application.Dtos.Channels;
using Oms.Application.Dtos.Shared;
using Oms.Application.Shared;
using Oms.Domain.ChannelsAggregate.Entities;
using Oms.Domain.Shared.Enums;
namespace Oms.Application.Services.Channels;
public partial class ServiceChannelService
{
/// <summary>
/// 读取渠道组映射,给列表和详情补充 groupName。
/// </summary>
private async Task<Dictionary<int, string>> GetGroupMapAsync()
{
return (await ServiceChannelGroupRepository.GetListAsync()).ToDictionary(x => x.Id, x => x.Name);
}
/// <summary>
/// 获取服务渠道分组,不存在时直接抛异常。
/// </summary>
private async Task<ServiceChannelGroup> GetGroupOrThrowAsync(int? id)
{
if (!id.HasValue || id.Value <= 0)
{
throw new Fake.Application.BusinessException("服务渠道分组不存在");
}
var group = await ServiceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == id.Value);
if (group == null)
{
throw new Fake.Application.BusinessException("服务渠道分组不存在");
}
return group;
}
/// <summary>
/// 校验服务渠道分组基础信息和名称唯一性。
/// </summary>
private async Task ValidateGroupInputAsync(ServiceChannelGroupSaveInput input, int? id)
{
if (string.IsNullOrWhiteSpace(input.Name))
{
throw new Fake.Application.BusinessException("分组名称不能为空");
}
if (input.AppId <= 0)
{
throw new Fake.Application.BusinessException("应用ID不能为空");
}
if (input.ApiServiceProviderId <= 0)
{
throw new Fake.Application.BusinessException("API服务商ID不能为空");
}
var existed = await ServiceChannelGroupRepository.GetByNameAsync(input.Name.Trim());
if (existed != null && existed.Id != id)
{
throw new Fake.Application.BusinessException("分组名称已存在");
}
}
/// <summary>
/// 校验渠道编码唯一性。
/// </summary>
private async Task ValidateChannelCodeAsync(string channelCode, int? id)
{
var existed = await ServiceChannelRepository.GetByChannelCodeAsync(channelCode);
if (existed != null && existed.Id != id)
{
throw new Fake.Application.BusinessException("渠道编码已存在");
}
}
/// <summary>
/// 获取服务渠道,不存在时直接抛异常。
/// </summary>
private async Task<ServiceChannel> GetEntityOrThrowAsync(int? id)
{
if (!id.HasValue || id.Value <= 0)
{
throw new Fake.Application.BusinessException("服务渠道不存在");
}
var entity = await ServiceChannelRepository.FirstOrDefaultAsync(x => x.Id == id.Value);
if (entity == null)
{
throw new Fake.Application.BusinessException("服务渠道不存在");
}
return entity;
}
/// <summary>
/// 获取买价项,不存在时直接抛异常。
/// </summary>
private async Task<ServiceChannelPrice> GetPriceOrThrowAsync(int? id)
{
if (!id.HasValue || id.Value <= 0)
{
throw new Fake.Application.BusinessException("买价项不存在");
}
var entity = await ServiceChannelRepository.GetPriceByIdAsync(id.Value);
if (entity == null)
{
throw new Fake.Application.BusinessException("买价项不存在");
}
return entity;
}
/// <summary>
/// 一次性装配买价项、因子和明细,避免接口层 N+1 查询。
/// </summary>
private async Task<List<ServiceChannelPriceListOutput>> GetPriceOutputsAsync(int serviceChannelId)
{
var prices = await ServiceChannelRepository.GetPricesAsync(serviceChannelId);
var priceIds = prices.Select(x => x.Id).ToArray();
var factorMap = (await ServiceChannelRepository.GetPriceFactorsAsync(priceIds))
.GroupBy(x => x.PriceId)
.ToDictionary(x => x.Key, x => x.OrderBy(item => item.SerialNumber).ToList());
var detailMap = (await ServiceChannelRepository.GetPriceDetailsAsync(priceIds))
.GroupBy(x => x.PriceId)
.ToDictionary(x => x.Key, x => x.OrderBy(item => item.Id).ToList());
return prices.Select(price => new ServiceChannelPriceListOutput
{
Id = price.Id,
ServiceChannelId = price.ServiceChannelId,
SerialNumber = price.SerialNumber,
Status = (byte)price.Status,
PriceName = price.PriceName,
CostItem = price.CostItem,
CostUnit = price.CostUnit,
Currency = price.Currency,
ChargingTime = price.ChargingTime,
ChargingCondition = price.ChargingCondition,
Factors = factorMap.TryGetValue(price.Id, out var factors) ? factors.Select(MapPriceFactorOutput).ToList() : [],
Details = detailMap.TryGetValue(price.Id, out var details) ? details.Select(MapPriceDetailOutput).ToList() : [],
CreateTime = price.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(price.UpdateTime)
}).ToList();
}
/// <summary>
/// 获取指定类型的发布记录。
/// </summary>
private async Task<OmsPageResult<ServiceChannelPublishRecordOutput>> GetPublishListAsync(ServiceChannelPublishListInput input, string operatorType)
{
await GetEntityOrThrowAsync(input.ServiceChannelId);
var pageIndex = OmsDocHelper.NormalizePage(input.Page);
var pageSize = OmsDocHelper.NormalizePageSize(input.PageSize);
var entities = await ServiceChannelRepository.GetPublishRecordsAsync(input.ServiceChannelId, operatorType, pageIndex, pageSize);
var total = await ServiceChannelRepository.CountPublishRecordsAsync(input.ServiceChannelId, operatorType);
return new OmsPageResult<ServiceChannelPublishRecordOutput>
{
Items = entities.Select(entity => new ServiceChannelPublishRecordOutput
{
Id = entity.Id,
ServiceChannelId = entity.ServiceChannelId,
Version = entity.Version,
OperatorType = entity.OperatorType,
Status = entity.Status,
PublishUser = entity.PublishUser,
PublishTime = entity.PublishTime,
Description = entity.Description,
Content = OmsDocHelper.DeserializeJsonObject(entity.Content),
CreateTime = entity.CreateTime
}).ToList(),
Total = total
};
}
/// <summary>
/// 为当前配置生成发布快照。
/// </summary>
private async Task CreatePublishRecordAsync(int serviceChannelId, string operatorType, string? description)
{
var entity = await GetEntityOrThrowAsync(serviceChannelId);
object snapshot = operatorType == PriceOperatorType
? new ServiceChannelPricePublishSnapshot
{
ServiceChannelId = serviceChannelId,
Prices = await GetPriceOutputsAsync(serviceChannelId)
}
: new ServiceChannelRulesPublishSnapshot
{
Channel = new ServiceChannelSaveInput
{
Id = entity.Id,
GroupId = entity.GroupId,
ChannelCode = entity.ChannelCode,
ChannelName = entity.ChannelName,
MinTime = entity.MinTime,
MaxTime = entity.MaxTime,
ForecastType = entity.ForecastType,
ForecastCode = entity.ForecastCode,
Description = entity.Description,
PreScript = entity.PreScript,
IsActive = entity.IsActive
}
};
// Content 中保存完整快照,回滚时直接从快照恢复。
await ServiceChannelRepository.InsertPublishRecordAsync(new ServiceChannelPublishRecord
{
ServiceChannelId = serviceChannelId,
Version = DateTime.Now.ToString("yyyyMMddHHmmss"),
OperatorType = operatorType,
Status = "生效",
PublishUser = string.IsNullOrWhiteSpace(CurrentUser.UserName) ? CurrentUser.GetId<int>().ToString() : CurrentUser.UserName!,
PublishTime = DateTime.Now,
Description = description,
Content = OmsDocHelper.SerializeJson(snapshot)
});
}
/// <summary>
/// 按发布记录回滚价格或规则配置。
/// </summary>
private async Task RollbackCoreAsync(ServiceChannelRollbackInput input)
{
var operatorType = NormalizeOperatorType(input.OperatorType);
await GetEntityOrThrowAsync(input.ServiceChannelId);
var record = await ServiceChannelRepository.GetPublishRecordByIdAsync(input.RecordId);
if (record == null || record.ServiceChannelId != input.ServiceChannelId || !string.Equals(record.OperatorType, operatorType, StringComparison.OrdinalIgnoreCase))
{
throw new Fake.Application.BusinessException("发布记录不存在");
}
if (operatorType == PriceOperatorType)
{
var snapshot = OmsDocHelper.DeserializeJson<ServiceChannelPricePublishSnapshot>(record.Content);
if (snapshot == null)
{
throw new Fake.Application.BusinessException("发布快照无效");
}
// 价格回滚直接恢复完整买价树。
await SavePriceAsync(new ServiceChannelPriceSaveAllInput
{
ServiceChannelId = input.ServiceChannelId,
Prices = snapshot.Prices.Select(MapPriceSaveInput).ToList()
});
return;
}
var rulesSnapshot = OmsDocHelper.DeserializeJson<ServiceChannelRulesPublishSnapshot>(record.Content);
if (rulesSnapshot == null)
{
throw new Fake.Application.BusinessException("发布快照无效");
}
var entity = await GetEntityOrThrowAsync(input.ServiceChannelId);
ApplyInput(entity, rulesSnapshot.Channel);
await ServiceChannelRepository.UpdateAsync(entity);
}
/// <summary>
/// 将保存入参映射到服务渠道实体。
/// </summary>
private static void ApplyInput(ServiceChannel entity, ServiceChannelSaveInput input)
{
entity.GroupId = input.GroupId;
entity.ChannelCode = input.ChannelCode;
entity.ChannelName = input.ChannelName;
entity.MinTime = input.MinTime;
entity.MaxTime = input.MaxTime;
entity.ForecastType = input.ForecastType;
entity.ForecastCode = input.ForecastCode;
entity.Description = input.Description;
entity.PreScript = input.PreScript;
entity.IsActive = input.IsActive;
}
private static ServiceChannelGroupListOutput MapGroupListOutput(ServiceChannelGroup entity)
{
return new ServiceChannelGroupListOutput
{
Id = entity.Id,
Name = entity.Name,
AppId = entity.AppId,
ApiServiceProviderId = entity.ApiServiceProviderId,
CreateTime = entity.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
};
}
private static ServiceChannelListOutput MapListOutput(ServiceChannel entity, IReadOnlyDictionary<int, string> groupMap)
{
return new ServiceChannelListOutput
{
Id = entity.Id,
GroupId = entity.GroupId,
GroupName = entity.GroupId.HasValue && groupMap.TryGetValue(entity.GroupId.Value, out var groupName) ? groupName : null,
ChannelCode = entity.ChannelCode,
ChannelName = entity.ChannelName,
MinTime = entity.MinTime,
MaxTime = entity.MaxTime,
ForecastType = entity.ForecastType,
ForecastCode = entity.ForecastCode,
Description = entity.Description,
IsActive = (byte)entity.IsActive,
CreateTime = entity.CreateTime,
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime)
};
}
private static ServiceChannelDetailOutput MapDetailOutput(ServiceChannel entity, IReadOnlyDictionary<int, string> groupMap)
{
return new ServiceChannelDetailOutput
{
Id = entity.Id,
GroupId = entity.GroupId,
GroupName = entity.GroupId.HasValue && groupMap.TryGetValue(entity.GroupId.Value, out var groupName) ? groupName : null,
ChannelCode = entity.ChannelCode,
ChannelName = entity.ChannelName,
MinTime = entity.MinTime,
MaxTime = entity.MaxTime,
ForecastType = entity.ForecastType,
ForecastCode = entity.ForecastCode,
Description = entity.Description,
PreScript = entity.PreScript,
IsActive = (byte)entity.IsActive,
CreateTime = entity.CreateTime,
CreateBy = entity.CreateUserId <= 0 ? null : entity.CreateUserId.ToString(),
UpdateTime = OmsDocHelper.NullIfDefault(entity.UpdateTime),
UpdateBy = entity.UpdateUserId <= 0 ? null : entity.UpdateUserId.ToString()
};
}
private static ChannelPriceFactorOutput MapPriceFactorOutput(ServiceChannelPriceFactor entity)
{
return new ChannelPriceFactorOutput
{
Id = entity.Id,
PriceId = entity.PriceId,
SerialNumber = entity.SerialNumber,
Factor = entity.Factor,
FactorValues = OmsDocHelper.DeserializeJson<List<string>>(entity.FactorValues) ?? []
};
}
private static ChannelPriceDetailOutput MapPriceDetailOutput(ServiceChannelPriceDetail entity)
{
return new ChannelPriceDetailOutput
{
Id = entity.Id,
PriceId = entity.PriceId,
FactorValuesJson = OmsDocHelper.DeserializeJsonObject(entity.FactorValuesJson),
CalculationFormula = entity.CalculationFormula,
CalculatedValue = entity.CalculatedValue,
CustomerGroupValues = OmsDocHelper.DeserializeJsonObject(entity.CustomerGroupValues)
};
}
private static List<ServiceChannelPriceFactor> BuildPriceFactors(IEnumerable<ChannelPriceFactorInput> inputs, int? priceId = null)
{
return inputs.Select(input => new ServiceChannelPriceFactor
{
PriceId = priceId ?? 0,
SerialNumber = input.SerialNumber,
Factor = input.Factor,
FactorValues = OmsDocHelper.SerializeJson(input.FactorValues)
}).ToList();
}
private static List<ServiceChannelPriceDetail> BuildPriceDetails(IEnumerable<ChannelPriceDetailInput> inputs, int? priceId = null)
{
return inputs.Select(input => new ServiceChannelPriceDetail
{
PriceId = priceId ?? 0,
FactorValuesJson = OmsDocHelper.SerializeJson(input.FactorValuesJson),
CalculationFormula = input.CalculationFormula,
CalculatedValue = input.CalculatedValue,
CustomerGroupValues = OmsDocHelper.SerializeJson(input.CustomerGroupValues)
}).ToList();
}
private static ServiceChannelPriceSaveInput MapPriceSaveInput(ServiceChannelPriceListOutput output)
{
return new ServiceChannelPriceSaveInput
{
Id = output.Id,
ServiceChannelId = output.ServiceChannelId,
SerialNumber = output.SerialNumber,
Status = (EnableStatus)output.Status,
PriceName = output.PriceName,
CostItem = output.CostItem,
CostUnit = output.CostUnit,
Currency = output.Currency,
ChargingTime = output.ChargingTime,
ChargingCondition = output.ChargingCondition,
Factors = output.Factors.Select(item => new ChannelPriceFactorInput
{
Id = item.Id,
SerialNumber = item.SerialNumber,
Factor = item.Factor,
FactorValues = item.FactorValues
}).ToList(),
Details = output.Details.Select(item => new ChannelPriceDetailInput
{
Id = item.Id,
FactorValuesJson = item.FactorValuesJson,
CalculationFormula = item.CalculationFormula,
CalculatedValue = item.CalculatedValue,
CustomerGroupValues = item.CustomerGroupValues
}).ToList()
};
}
private static string NormalizeOperatorType(string? operatorType)
{
var normalized = operatorType?.Trim().ToLowerInvariant();
return normalized switch
{
PriceOperatorType => PriceOperatorType,
RulesOperatorType => RulesOperatorType,
_ => throw new Fake.Application.BusinessException("不支持的发布类型")
};
}
private sealed class ServiceChannelPricePublishSnapshot
{
public int ServiceChannelId { get; set; }
public List<ServiceChannelPriceListOutput> Prices { get; set; } = [];
}
private sealed class ServiceChannelRulesPublishSnapshot
{
public ServiceChannelSaveInput Channel { get; set; } = new();
}
}