404 lines
17 KiB
C#
404 lines
17 KiB
C#
using SJZY.Expand.ABP.Core.PublicMethords;
|
||
using SJZY.Track.Application.Contract.Dtos.Warning;
|
||
using SJZY.Track.Application.Contract.Dtos.Warning.Transport;
|
||
using SJZY.Track.Application.Helpers;
|
||
using SJZY.Track.Application.Services.Warning.Handlers;
|
||
using SJZY.Track.Application.Services.Warning.Model;
|
||
using SJZY.Track.Domain.Aggregates.KPIAreaAggregate;
|
||
using SJZY.Track.Domain.Aggregates.OrderAggregate;
|
||
using SJZY.Track.Domain.Aggregates.TrackDetailsAggregate;
|
||
using SJZY.Track.Domain.Aggregates.TrackStatusAggregate;
|
||
using SJZY.Track.Domain.Aggregates.WarningAggregate;
|
||
using SJZY.Track.Domain.Aggregates.WarningAggregate.Transport;
|
||
using SJZY.Track.Domain.Dtos.Warning;
|
||
using SJZY.Track.Domain.Enums;
|
||
using SJZY.Track.Domain.Extensions;
|
||
using SJZY.Track.Domain.Queries;
|
||
|
||
namespace SJZY.Track.Application.Services.Warning;
|
||
|
||
/// <summary>
|
||
/// 运输时效预警服务
|
||
/// </summary>
|
||
[ApiExplorerSettings(GroupName = "预警服务")]
|
||
[Route("Warning/Transport")]
|
||
public class TransportWarningService : ApplicationService
|
||
{
|
||
private readonly IOrderRepository _orderRepository;
|
||
private readonly IVExternalSubjectQueries _vExternalSubjectQueries;
|
||
private readonly IVSystemQueries _vSystemQueries;
|
||
private readonly IWarningRuleRepository _warningRuleRepository;
|
||
private readonly IWarningDetailsEsRepository _warningDetailsEsRepository;
|
||
private readonly ITransportWarningHandler _transportWarningHandler;
|
||
private readonly ITrackDetailsEsRepository _trackDetailsEsRepository;
|
||
private readonly ITrackStatusRepository _trackStatusRepository;
|
||
private readonly IKpiAreaRepository _kpiAreaRepository;
|
||
|
||
public TransportWarningService(IWarningRuleRepository warningRuleRepository, IWarningDetailsEsRepository warningDetailsEsRepository, ITransportWarningHandler transportWarningHandler, ITrackDetailsEsRepository trackDetailsEsRepository, ITrackStatusRepository trackStatusRepository, IKpiAreaRepository kpiAreaRepository, IOrderRepository orderRepository, IVExternalSubjectQueries vExternalSubjectQueries, IVSystemQueries vSystemQueries)
|
||
{
|
||
_warningRuleRepository = warningRuleRepository;
|
||
_warningDetailsEsRepository = warningDetailsEsRepository;
|
||
_transportWarningHandler = transportWarningHandler;
|
||
_trackDetailsEsRepository = trackDetailsEsRepository;
|
||
_trackStatusRepository = trackStatusRepository;
|
||
_kpiAreaRepository = kpiAreaRepository;
|
||
_orderRepository = orderRepository;
|
||
_vExternalSubjectQueries = vExternalSubjectQueries;
|
||
_vSystemQueries = vSystemQueries;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取预警规则场景
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("GetTransportWarningScene")]
|
||
public async Task<UnifyResultDto<List<TransportWarningSceneDto>>> GetTransportWarningScene(GetTransportWarningSceneInput input)
|
||
{
|
||
var entities = await _warningRuleRepository.GetTransportSceneAsync(input.WarningId);
|
||
if (entities.Count == 0) return new UnifyResultDto<List<TransportWarningSceneDto>>(true, Empty<TransportWarningSceneDto>.List);
|
||
|
||
var areaIds = entities
|
||
.Select(x => x.AreaId)
|
||
.Where(x => x != null)
|
||
.Select(x => x!.Value)
|
||
.ToHashSet();
|
||
|
||
// todo:加缓存
|
||
Dictionary<int, string>? areaNameDict = null;
|
||
if (areaIds.Count > 0)
|
||
{
|
||
var areas = await _kpiAreaRepository.GetListAsync(x => areaIds.Contains(x.Id), y => new
|
||
{
|
||
y.Id,
|
||
y.Name
|
||
});
|
||
areaNameDict = areas.ToDictionary(x => x.Id, y => y.Name);
|
||
}
|
||
|
||
var map = entities.Select(x => new TransportWarningSceneDto
|
||
{
|
||
Id = x.Id,
|
||
WarningId = x.WarningId,
|
||
Summary = BuildSummary(x),
|
||
IsTransit = x.IsTransit,
|
||
CargoType = x.CargoType,
|
||
LeavePort = x.LeavePort,
|
||
ProductCode = x.ProductCode,
|
||
Months = x.Months,
|
||
AreaId = x.AreaId,
|
||
AreaName = x.AreaId.HasValue ? areaNameDict?.GetValueOrDefault(x.AreaId.Value) : null
|
||
});
|
||
|
||
return new UnifyResultDto<List<TransportWarningSceneDto>>(true, map.ToList());
|
||
|
||
// 构建场景摘要
|
||
string BuildSummary(TransportWarningSceneEntity entity)
|
||
{
|
||
const string separator = "/";
|
||
// var sb = new StringBuilder();
|
||
var list = new List<string>(6);
|
||
// 货物类型
|
||
if (!string.IsNullOrWhiteSpace(entity.CargoType))
|
||
list.Add($"{entity.CargoType}");
|
||
// 月份
|
||
if (!string.IsNullOrWhiteSpace(entity.Months))
|
||
list.Add($"{entity.Months.Replace("/", ",")}月份");
|
||
// 中转
|
||
if (entity.IsTransit != null)
|
||
list.Add($"中转为{(entity.IsTransit.Value ? "是" : "否")}");
|
||
// 起运港口
|
||
if (!string.IsNullOrWhiteSpace(entity.LeavePort))
|
||
list.Add($"{entity.LeavePort}");
|
||
// 产品编码
|
||
if (!string.IsNullOrWhiteSpace(entity.ProductCode))
|
||
list.Add($"{entity.ProductCode}");
|
||
// 区域名称
|
||
if (entity.AreaId != null && areaNameDict != null)
|
||
{
|
||
var areaName = areaNameDict.GetValueOrDefault(entity.AreaId.Value);
|
||
if (!string.IsNullOrWhiteSpace(areaName))
|
||
list.Add($"{areaName}");
|
||
}
|
||
|
||
return string.Join(separator, list);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取运输时效场景规则项目
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("GetTransportWarningSceneRuleItem")]
|
||
public async Task<UnifyResultDto<List<TransportWarningSceneRuleItemDto>>> GetTransportWarningSceneRuleItem(GetTransportWarningSceneRuleItemInput input)
|
||
{
|
||
var entities = await _warningRuleRepository.GetTransportSceneRuleItemsAsync(input.WarningId, input.SceneId);
|
||
|
||
// todo:缓存优化
|
||
var trackStatusIds = entities.SelectMany(x => new List<int>(2)
|
||
{
|
||
x.StartTrackStatusId,
|
||
x.EndTrackStatusId
|
||
}).ToHashSet();
|
||
var trackStatusDict = (await _trackStatusRepository.GetListAsync(x => trackStatusIds.Contains(x.Id), x => new
|
||
{
|
||
x.Id,
|
||
x.Status
|
||
})).ToDictionary(x => x.Id, y => y.Status);
|
||
|
||
var map = entities.Select(x => new TransportWarningSceneRuleItemDto
|
||
{
|
||
Id = x.Id,
|
||
StartTrackStatusId = x.StartTrackStatusId,
|
||
StartTrackStatus = trackStatusDict.GetValueOrDefault(x.StartTrackStatusId, GlobalConst.EmptyString),
|
||
EndTrackStatusId = x.EndTrackStatusId,
|
||
EndTrackStatus = trackStatusDict.GetValueOrDefault(x.EndTrackStatusId, GlobalConst.EmptyString),
|
||
TransportTimeSpan = x.TransportTimeSpan,
|
||
StartTrackWarningTimeSpan = x.StartTrackWarningTimeSpan,
|
||
NotifyUsers = x.NotifyUsers.ToObject<List<NotifyUserDto>>()
|
||
});
|
||
|
||
return new UnifyResultDto<List<TransportWarningSceneRuleItemDto>>(true, map.ToList());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分页获取运输时效预警明细
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("GetPageTransportWarningDetails")]
|
||
public async Task<UnifyPageResultDto<TransportWarningDetailsDto>> GetPageTransportWarningDetails(GetPageTransportWarningDetailsInput input)
|
||
{
|
||
var vo = ObjectMapper.Map<GetPageTransportWarningDetailsInput, GetPageTransportWarningDetailsInputVO>(input);
|
||
// 存储只存即将超时,对于已超时的则是查出计算后过滤
|
||
if (input.Status == (int) TransportWarningStatus.已超时)
|
||
vo.Status = (int) TransportWarningStatus.即将超时;
|
||
var result = await _warningDetailsEsRepository.GetPageTransportWarningDetailsAsync(vo);
|
||
if (result.List.Count == 0)
|
||
{
|
||
return new UnifyPageResultDto<TransportWarningDetailsDto>(new List<TransportWarningDetailsDto>(), input.PageIndex, input.PageSize, 0);
|
||
}
|
||
|
||
// 查最新轨迹
|
||
var orderIds = result.List.Select(x => x.OrderId).ToHashSet();
|
||
var orderIdTrackDict = await _trackDetailsEsRepository.GetLatestDictAsync(orderIds);
|
||
|
||
// todo:缓存优化
|
||
// 订单类型
|
||
var orderTypeDict = await _orderRepository.GetTrackOrderTypeDictAsync();
|
||
// 客户简称
|
||
var customIds = result.List.Select(x => x.CustomId).ToHashSet();
|
||
var customDict = (await _vExternalSubjectQueries
|
||
.GetListAsync(x => customIds.Contains(x.Id), x => new
|
||
{
|
||
x.Id,
|
||
x.Abbreviation
|
||
})).ToDictionary(x => x.Id, x => x.Abbreviation);
|
||
// 子系统
|
||
var subSystemIds = result.List.Select(x => x.SubSystemId).ToHashSet();
|
||
var subSystemDict = (await _vSystemQueries
|
||
.GetListAsync(x => subSystemIds.Contains(x.Id), x => new
|
||
{
|
||
x.Id,
|
||
x.CnName
|
||
})).ToDictionary(x => x.Id, x => x.CnName);
|
||
|
||
var map = result.List.Select(x =>
|
||
{
|
||
var orderIdTrack = orderIdTrackDict.GetValueOrDefault(x.OrderId);
|
||
return new TransportWarningDetailsDto
|
||
{
|
||
EsId = x.Id,
|
||
SubSystemId = x.SubSystemId,
|
||
SubSystemName = subSystemDict.GetValueOrDefault(x.SubSystemId, GlobalConst.EmptyString),
|
||
CustomId = x.CustomId,
|
||
CustomName = customDict.GetValueOrDefault(x.CustomId, GlobalConst.EmptyString),
|
||
OrderId = x.OrderId,
|
||
OrderNo = x.OrderNo,
|
||
OrderType = x.OrderType,
|
||
OrderTypeName = orderTypeDict.GetValueOrDefault(x.OrderType.ToString(), GlobalConst.EmptyString),
|
||
NotifyUserNames = ToUserNames(x.NotifyUsers),
|
||
|
||
// 最新轨迹 (不冗余)
|
||
LatestTrackContent = orderIdTrack?.TrackContent,
|
||
LatestTrackTime = orderIdTrack?.TrackTime,
|
||
LatestTrackTimeZone = DateTimeHelper.ConvertFloatToTimeZone(orderIdTrack?.TrackTimeZone),
|
||
LatestTrackPlace = orderIdTrack?.TrackPlace,
|
||
|
||
// 起始轨迹
|
||
StartTrackStatus = x.StartTrackStatus,
|
||
StartTrackContent = x.StartTrackContent,
|
||
StartTrackTime = x.StartTrackTime,
|
||
StartTrackTimeZone = DateTimeHelper.ConvertFloatToTimeZone(x.StartTrackTimeZone),
|
||
|
||
// 截止轨迹
|
||
EndTrackStatus = x.EndTrackStatus,
|
||
EndTrackContent = x.EndTrackContent,
|
||
EndTrackTime = x.EndTrackTime,
|
||
EndTrackTimeZone = DateTimeHelper.ConvertFloatToTimeZone(x.EndTrackTimeZone),
|
||
|
||
WarningName = x.WarningName,
|
||
WarningRuleDescription = x.WarningRuleDescription,
|
||
|
||
// 累计运输时间
|
||
CumulativeTimeSpan = FormatCumulativeTimeSpan(x),
|
||
|
||
// 状态动态计算
|
||
Status = x.CalcStatus(),
|
||
CreateTime = x.CreateTime
|
||
};
|
||
});
|
||
|
||
// 计算后的状态过滤
|
||
var filtered = map.WhereIf(input.Status > 0, x => x.Status == input.Status);
|
||
|
||
// 按状态组+组内时间降序,分割状态组 g1:(即将超时/已超时) g2:(已完成/超时完成/已关闭) 确保g1总是在g2之前
|
||
var sorted = filtered
|
||
.OrderBy(x => x.Status is (int) TransportWarningStatus.即将超时 or (int) TransportWarningStatus.已超时 ? 1 : 2)
|
||
.ThenByDescending(x => x.CreateTime) //同状态组按时间降序
|
||
.ToList();
|
||
|
||
return new UnifyPageResultDto<TransportWarningDetailsDto>(
|
||
sorted,
|
||
result.Pagination.PageIndex,
|
||
result.Pagination.PageSize,
|
||
sorted.Count);
|
||
|
||
// 通知用户提取用户名称
|
||
string ToUserNames(string notifyUsers)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(notifyUsers))
|
||
return string.Empty;
|
||
return string.Join(',', notifyUsers.ToObject<List<NotifyUserDto>>().Select(x => x.UserName));
|
||
}
|
||
|
||
// 格式化累计运输时间为文本形式
|
||
string FormatCumulativeTimeSpan(TransportWarningDetailsIndex detail)
|
||
{
|
||
// 如果是完成态则直接取缓存的累计运输时间
|
||
var timeSpan = detail.CanCompleted()
|
||
? TimeSpan.FromMilliseconds(detail.CumulativeTimeSpan)
|
||
: DateTime.Now.Subtract(detail.StartTrackTime);
|
||
|
||
return $"{timeSpan.Days:00}天{timeSpan.Hours:00}小时{timeSpan.Minutes:00}分钟";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 新增/更新 运输时效场景
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("UpsertTransportWarningScene")]
|
||
public async Task<UnifyResultDto<IdBaseDto>> UpsertTransportWarningScene(UpsertTransportWarningSceneInput input)
|
||
{
|
||
var rule = await _warningRuleRepository.GetWarningRuleByIdAsync(input.WarningId, WarningRuleType.运输时效预警);
|
||
if (rule == null)
|
||
{
|
||
throw new BusinessException(message: $"预警规则主体不存在,WarningId=[{input.WarningId}]");
|
||
}
|
||
|
||
var postBo = TransportWarningSceneBO.From(input);
|
||
// 查出旧场景
|
||
var oldScene = await _warningRuleRepository.GetTransportSceneAsync(input.WarningId, input.Id);
|
||
var oldBo = oldScene != null ? TransportWarningSceneBO.From(oldScene) : null;
|
||
|
||
// 如果有月份则排序之后在写入
|
||
if (!string.IsNullOrWhiteSpace(postBo.Months))
|
||
{
|
||
var sorted = postBo.Months.Split('/').Select(int.Parse).OrderBy(x => x).ToList();
|
||
postBo.Months = string.Join('/', sorted);
|
||
}
|
||
|
||
// 更新或者新增
|
||
var id = await _warningRuleRepository.UpsertTransportSceneReturnIdAsync(postBo.ToEntity());
|
||
|
||
if (oldBo != null)
|
||
await _transportWarningHandler.HandleSceneChangedAsync(rule, oldBo, postBo);
|
||
|
||
return new UnifyResultDto<IdBaseDto>(true, new IdBaseDto
|
||
{
|
||
Id = id
|
||
});
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 删除运输时效规则场景
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("DeleteTransportWarningScene")]
|
||
public async Task<UnifyResultDto> DeleteTransportWarningScene(DeleteTransportWarningSceneInput input)
|
||
{
|
||
var rule = await _warningRuleRepository.GetWarningRuleByIdAsync(input.WarningId, WarningRuleType.运输时效预警);
|
||
if (rule == null)
|
||
{
|
||
throw new BusinessException(message: $"预警规则主体不存在,WarningId=[{input.WarningId}]");
|
||
}
|
||
|
||
var scene = await _warningRuleRepository.GetTransportSceneAsync(input.WarningId, input.Id);
|
||
if (scene == null)
|
||
{
|
||
throw new BusinessException(message: $"预警规则场景不存在,WarningId=[{input.WarningId}]");
|
||
}
|
||
|
||
// 执行删除
|
||
await _warningRuleRepository.DeleteTransportSceneAsync(input.WarningId, input.Id);
|
||
|
||
// 处理删除后
|
||
await _transportWarningHandler.HandleSceneDeletedAsync(rule, TransportWarningSceneBO.From(scene));
|
||
|
||
return new UnifyResultDto(true);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 编辑预警规则项目(全量)
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("EditTransportWarningSceneRuleItem")]
|
||
public async Task<UnifyResultDto> EditTransportWarningSceneRuleItem(EditTransportWarningSceneRuleItemInput input)
|
||
{
|
||
// 起始轨迹生成预警时间不能大于时效
|
||
if (input.Items.Count > 0 && input.Items.Any(x => x.StartTrackWarningTimeSpan >= x.TransportTimeSpan))
|
||
{
|
||
throw new BusinessException(message: "起始轨迹生成预警时间不能大于等于时效,请检查");
|
||
}
|
||
|
||
var rule = await _warningRuleRepository.GetWarningRuleByIdAsync(input.WarningId, WarningRuleType.运输时效预警);
|
||
if (rule == null)
|
||
{
|
||
throw new BusinessException(message: $"预警规则主体不存在,WarningId=[{input.WarningId}]");
|
||
}
|
||
|
||
var ruleScene = await _warningRuleRepository.GetTransportSceneAsync(input.WarningId, input.SceneId);
|
||
if (ruleScene == null)
|
||
{
|
||
throw new BusinessException(message: $"运输时效规则场景不存在,WarningIdId=[{input.WarningId}] SceneId=[{input.SceneId}]");
|
||
}
|
||
|
||
var postRuleItems = TransportWarningSceneRuleItemBO.From(input);
|
||
|
||
var tuples = postRuleItems.Select(x => (x.StartTrackStatusId, x.EndTrackStatusId)).ToHashSet();
|
||
// 规则项目间不允许出现起始轨迹和截止轨迹都重复的项目
|
||
if (tuples.Count < postRuleItems.Count)
|
||
{
|
||
throw new BusinessException(message: "不允许出现轨迹状态和终止轨迹状态都重复的规则项目");
|
||
}
|
||
|
||
// 起始和截止不能相同
|
||
if (tuples.Any(x => x.StartTrackStatusId == x.EndTrackStatusId))
|
||
{
|
||
throw new BusinessException(message: "同一规则项目起始轨迹状态和截止轨迹状态不能相同");
|
||
}
|
||
|
||
var sceneBo = TransportWarningSceneBO.From(ruleScene);
|
||
await _transportWarningHandler.HandleEditAsync(rule, sceneBo, postRuleItems);
|
||
|
||
return new UnifyResultDto(true);
|
||
}
|
||
}
|