438 lines
17 KiB
C#
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();
|
|
}
|
|
}
|