track/src/SJZY.Track.Application/Services/Warning/TransportWarningService.cs
hardstylewyl ca35a86407 add-validation:补充运输预警规则项目编辑(全量)校验
- 不同项目间不能存在起始和截止都重复的
 - 同一项目的起始和截止不允许重复
2025-03-27 15:55:35 +08:00

404 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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