382 lines
15 KiB
C#
382 lines
15 KiB
C#
using Fake.DomainDrivenDesign.Repositories.SqlSugarCore;
|
||
using SqlSugar;
|
||
using TrackCenter.Domain.Aggregates.OrderAggregate;
|
||
using TrackCenter.Domain.Aggregates.TrackContentAggregate;
|
||
using TrackCenter.Domain.Aggregates.TrackFlagAggregate;
|
||
using TrackCenter.Domain.Aggregates.TrackPullAggregate;
|
||
using TrackCenter.Domain.Aggregates.TrackStatusAggregate;
|
||
using TrackCenter.Domain.Dtos.Order;
|
||
using TrackCenter.Domain.Dtos.TrackContents;
|
||
using TrackCenter.Domain.Dtos.TrackPull;
|
||
using TrackCenter.Repository;
|
||
|
||
namespace TrackCenter.Application.Queries;
|
||
|
||
/// <summary>
|
||
/// 提供服务相关查询
|
||
/// </summary>
|
||
public class ProvideQueries : SqlSugarRootlessRepository<TrackCenterDbContext>, ITransientDependency
|
||
{
|
||
public ISqlSugarClient Context => GetDbContextAsync().Result;
|
||
|
||
public async Task<List<string>> MapTrackCodesAsync(string upAppKey, string appKey, params string[] trackCodes)
|
||
{
|
||
// 根据上游code得到下游code
|
||
return await Context.Queryable<TrackContentMapEntity>()
|
||
.Where(x => x.AppKey == appKey && x.MapAppKey == upAppKey &&
|
||
trackCodes.Contains(x.MapTrackContentCode))
|
||
.Select(x => x.TrackContentCode)
|
||
.ToListAsync();
|
||
}
|
||
|
||
public async Task<List<TrackCodeMapVO>> GetTrackCodeMapsAsync(string upAppKey, string appKey,
|
||
params string[] trackCodes)
|
||
{
|
||
// 根据上游code得到下游code
|
||
return await Context.Queryable<TrackContentMapEntity>()
|
||
.Where(x => x.AppKey == appKey && x.MapAppKey == upAppKey &&
|
||
trackCodes.Contains(x.MapTrackContentCode))
|
||
.Select(x => new TrackCodeMapVO()
|
||
{
|
||
AppKey = x.AppKey,
|
||
TrackContentCode = x.TrackContentCode,
|
||
MapAppKey = x.MapAppKey,
|
||
MapTrackContentCode = x.MapTrackContentCode
|
||
})
|
||
.ToListAsync();
|
||
}
|
||
|
||
public async Task<List<OrderWithServiceProviderVO>> FindOrderWithServiceProviderAsync(string appKey,
|
||
List<string> orderNos)
|
||
{
|
||
var orderRows = await Context.Queryable<OrderEntity>()
|
||
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
|
||
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
|
||
.Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo)
|
||
&& b.StartTime <= DateTime.Now && b.EndTime >= DateTime.Now)
|
||
.Select((a, b) => new
|
||
{
|
||
CustomerOrderNo = a.OrderNo,
|
||
a.TrackNo,
|
||
a.ThirdPartyOrderNumber,
|
||
a.Customer,
|
||
a.OrderType,
|
||
a.AppKey,
|
||
ServiceProviderCode = b.ServiceProviderCode,
|
||
})
|
||
.ToListAsync();
|
||
|
||
if (orderRows.Count == 0) return [];
|
||
|
||
var appKeys = orderRows.Select(x => x.AppKey).Distinct().ToList();
|
||
var serviceProviderCodes = orderRows.Select(x => x.ServiceProviderCode).Distinct().ToList();
|
||
|
||
// 拉取优先级:精确客户优先于 * 兜底;同级按 Priority 升序
|
||
var priorities = await Context.Queryable<TrackPullConfigEntity>()
|
||
.InnerJoin<TrackPullPriorityEntity>((c, d) => c.Id == d.TrackPullConfigId)
|
||
.Where((c, d) => appKeys.Contains(c.AppKey) && serviceProviderCodes.Contains(c.ServiceProviderCode))
|
||
.Select((c, d) => new
|
||
{
|
||
c.AppKey,
|
||
c.ServiceProviderCode,
|
||
c.OrderType,
|
||
d.Customer,
|
||
d.OrderNoType,
|
||
d.Priority,
|
||
})
|
||
.ToListAsync();
|
||
|
||
return orderRows.Select(o =>
|
||
{
|
||
var matched = priorities
|
||
.Where(p => p.AppKey == o.AppKey
|
||
&& p.ServiceProviderCode == o.ServiceProviderCode
|
||
&& p.OrderType == o.OrderType
|
||
&& (p.Customer == o.Customer || p.Customer == GlobalConst.All))
|
||
.OrderBy(p => p.Customer == o.Customer ? 0 : 1)
|
||
.ThenBy(p => p.Priority)
|
||
.FirstOrDefault();
|
||
|
||
return new OrderWithServiceProviderVO
|
||
{
|
||
OrderNo = ResolvePullOrderNo(matched?.OrderNoType, o.CustomerOrderNo, o.TrackNo,
|
||
o.ThirdPartyOrderNumber),
|
||
TrackNo = o.TrackNo,
|
||
OtherNo = o.ThirdPartyOrderNumber,
|
||
OrderType = o.OrderType,
|
||
AppKey = o.AppKey,
|
||
ServiceProviderCode = o.ServiceProviderCode,
|
||
};
|
||
}).ToList();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按拉取优先级配置的单号类型解析下游拉取主单号。
|
||
/// </summary>
|
||
private static string ResolvePullOrderNo(TrackOrderNoType? orderNoType, string customerOrderNo, string? trackNo,
|
||
string? otherNo)
|
||
{
|
||
return orderNoType switch
|
||
{
|
||
TrackOrderNoType.客户单号 => customerOrderNo,
|
||
TrackOrderNoType.跟踪单号 => trackNo.IsNullOrWhiteSpace() ? customerOrderNo : trackNo!,
|
||
TrackOrderNoType.其他单号 => otherNo.IsNullOrWhiteSpace() ? customerOrderNo : otherNo!,
|
||
_ => customerOrderNo,
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手工刷新诊断:区分可刷新订单与不可刷新原因
|
||
/// </summary>
|
||
public async Task<(List<OrderWithServiceProviderVO> CanRefresh, List<string> SkipReasons)>
|
||
DiagnoseManualRefreshAsync(string appKey, List<string> orderNos)
|
||
{
|
||
var now = DateTime.Now;
|
||
var canRefresh = await FindOrderWithServiceProviderAsync(appKey, orderNos);
|
||
|
||
var eligibleOrderNos = await Context.Queryable<OrderEntity>()
|
||
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
|
||
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
|
||
.Where((a, b) => !a.IsCompleted && orderNos.Contains(a.OrderNo)
|
||
&& b.StartTime <= now && b.EndTime >= now)
|
||
.Select((a, b) => a.OrderNo)
|
||
.Distinct()
|
||
.ToListAsync();
|
||
var eligibleSet = eligibleOrderNos.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||
|
||
var orderStates = await Context.Queryable<OrderEntity>()
|
||
.LeftJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
|
||
.WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey)
|
||
.Where(a => orderNos.Contains(a.OrderNo))
|
||
.Select((a, b) => new
|
||
{
|
||
a.OrderNo,
|
||
a.IsCompleted,
|
||
PullStatusId = SqlFunc.IsNull(b.Id, 0),
|
||
b.StartTime,
|
||
b.EndTime
|
||
})
|
||
.ToListAsync();
|
||
|
||
var skipReasons = new List<string>();
|
||
var foundSet = orderStates.Select(x => x.OrderNo).ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||
|
||
foreach (var orderNo in orderNos)
|
||
{
|
||
if (eligibleSet.Contains(orderNo))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (!foundSet.Contains(orderNo))
|
||
{
|
||
skipReasons.Add($"{orderNo}:订单不存在或不属于当前应用");
|
||
continue;
|
||
}
|
||
|
||
var rows = orderStates.Where(x => string.Equals(x.OrderNo, orderNo, StringComparison.OrdinalIgnoreCase))
|
||
.ToList();
|
||
if (rows.Any(x => x.IsCompleted))
|
||
{
|
||
skipReasons.Add($"{orderNo}:订单已完成,不再拉取轨迹");
|
||
continue;
|
||
}
|
||
|
||
var pullRows = rows.Where(x => x.PullStatusId > 0).ToList();
|
||
if (pullRows.Count == 0)
|
||
{
|
||
skipReasons.Add($"{orderNo}:无拉取状态(未配置拉取或尚未生成拉取任务)");
|
||
continue;
|
||
}
|
||
|
||
if (!pullRows.Any(x => x.StartTime <= now && x.EndTime >= now))
|
||
{
|
||
var latest = pullRows.OrderByDescending(x => x.EndTime).First();
|
||
skipReasons.Add(
|
||
$"{orderNo}:不在拉取时间窗口内({latest.StartTime:yyyy-MM-dd HH:mm} ~ {latest.EndTime:yyyy-MM-dd HH:mm})");
|
||
continue;
|
||
}
|
||
|
||
skipReasons.Add($"{orderNo}:暂不可刷新,请检查拉取配置/服务商映射");
|
||
}
|
||
|
||
return (canRefresh, skipReasons);
|
||
}
|
||
|
||
public async Task<List<OrderWithServiceProviderVO>> FindOrderWithServiceProviderAsync(
|
||
TrackPullConfigVo config,
|
||
int pageIndex, int pageSize = 1000)
|
||
{
|
||
var rows = await Context.Queryable<OrderEntity>()
|
||
.InnerJoin<OrderPullStatusEntity>((a, b) => a.Id == b.OrderId)
|
||
.Where((a, b) => b.ServiceProviderCode == config.ServiceProviderCode
|
||
&& a.AppKey == config.AppKey
|
||
&& !a.IsCompleted
|
||
&& b.StartTime <= DateTime.Now
|
||
&& b.EndTime >= DateTime.Now
|
||
)
|
||
.WhereIF(config.Customer != GlobalConst.All, a => a.Customer == config.Customer)
|
||
.Select((a, b) => new
|
||
{
|
||
CustomerOrderNo = a.OrderNo,
|
||
a.TrackNo,
|
||
a.ThirdPartyOrderNumber,
|
||
a.OrderType,
|
||
a.AppKey,
|
||
ServiceProviderCode = b.ServiceProviderCode
|
||
})
|
||
.Skip((pageIndex - 1) * pageSize)
|
||
.Take(pageSize)
|
||
.ToListAsync();
|
||
|
||
return rows.Select(o => new OrderWithServiceProviderVO
|
||
{
|
||
OrderNo = ResolvePullOrderNo(config.OrderNoType, o.CustomerOrderNo, o.TrackNo, o.ThirdPartyOrderNumber),
|
||
TrackNo = o.TrackNo,
|
||
OtherNo = o.ThirdPartyOrderNumber,
|
||
OrderType = o.OrderType,
|
||
AppKey = o.AppKey,
|
||
ServiceProviderCode = o.ServiceProviderCode
|
||
}).ToList();
|
||
}
|
||
|
||
public async Task<TrackContentVO?> GetTrackContentAsync(string appKey, string trackCode)
|
||
{
|
||
var content = await Context.Queryable<TrackContentEntity>()
|
||
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
|
||
.Where(x => x.AppKey == appKey && x.Code == trackCode)
|
||
.Select((x, y) => new TrackContentVO
|
||
{
|
||
Id = x.Id,
|
||
AppKey = x.AppKey,
|
||
TrackStatusId = x.TrackStatusId,
|
||
TrackStatus = y.Status,
|
||
TrackStatusCode = y.Code,
|
||
TrackContentCode = x.Code,
|
||
TrackContentId = x.Id,
|
||
TrackContent = x.Content,
|
||
TrackEnContent = x.EnContent,
|
||
IsCompleted = y.IsCompleted,
|
||
IsRepeatable = x.IsRepeatable,
|
||
IsExternal = x.IsExternal,
|
||
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
|
||
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
|
||
IsToMq = y.IsToMq,
|
||
TrackFlagId = x.TrackFlagId
|
||
})
|
||
.FirstAsync();
|
||
|
||
if (content == null) return null;
|
||
|
||
// 填充轨迹标记
|
||
await FillTrackContentFlagsAsync(new List<TrackContentVO>(1)
|
||
{
|
||
content
|
||
});
|
||
|
||
return content;
|
||
}
|
||
|
||
public async Task<List<TrackContentVO>> GetTrackContentsAsync(string[] appKeys, string[] trackCodes)
|
||
{
|
||
var contents = await Context.Queryable<TrackContentEntity>()
|
||
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
|
||
.Where(x => appKeys.Contains(x.AppKey) && trackCodes.Contains(x.Code))
|
||
.Select((x, y) => new TrackContentVO
|
||
{
|
||
Id = x.Id,
|
||
AppKey = x.AppKey,
|
||
TrackStatusId = x.TrackStatusId,
|
||
TrackStatus = y.Status,
|
||
TrackStatusCode = y.Code,
|
||
TrackContentCode = x.Code,
|
||
TrackContentId = x.Id,
|
||
TrackContent = x.Content,
|
||
TrackEnContent = x.EnContent,
|
||
IsCompleted = y.IsCompleted,
|
||
IsRepeatable = x.IsRepeatable,
|
||
IsExternal = x.IsExternal,
|
||
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
|
||
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
|
||
IsToMq = y.IsToMq,
|
||
TrackFlagId = x.TrackFlagId
|
||
})
|
||
.ToListAsync();
|
||
|
||
// 填充轨迹标记
|
||
await FillTrackContentFlagsAsync(contents);
|
||
|
||
return contents;
|
||
}
|
||
|
||
public async Task<List<TrackContentVO>> GetTrackContentAsync(HashSet<int> contentIds)
|
||
{
|
||
var contents = await Context.Queryable<TrackContentEntity>()
|
||
.LeftJoin<TrackStatusEntity>((x, y) => x.TrackStatusId == y.Id)
|
||
.Where(x => contentIds.Contains(x.Id))
|
||
.Select((x, y) => new TrackContentVO
|
||
{
|
||
Id = x.Id,
|
||
AppKey = x.AppKey,
|
||
TrackStatusId = x.TrackStatusId,
|
||
TrackStatus = y.Status,
|
||
TrackStatusCode = y.Code,
|
||
TrackContentCode = x.Code,
|
||
TrackContentId = x.Id,
|
||
TrackContent = x.Content,
|
||
TrackEnContent = x.EnContent,
|
||
IsCompleted = y.IsCompleted,
|
||
IsRepeatable = x.IsRepeatable,
|
||
IsExternal = x.IsExternal,
|
||
IsAllowManuallyUpdate = x.IsAllowManuallyUpdate,
|
||
IsAllowManuallyCreate = x.IsAllowManuallyCreate,
|
||
IsToMq = y.IsToMq,
|
||
TrackFlagId = x.TrackFlagId
|
||
})
|
||
.ToListAsync();
|
||
|
||
// 在轨迹内容填充轨迹标记
|
||
await FillTrackContentFlagsAsync(contents);
|
||
|
||
return contents;
|
||
}
|
||
|
||
private async Task FillTrackContentFlagsAsync(List<TrackContentVO> contents)
|
||
{
|
||
// 1.构建轨迹内容和轨迹标记Ids的映射关系
|
||
var contentFlagIdsDict = contents
|
||
.Where(x => !string.IsNullOrWhiteSpace(x.TrackFlagId))
|
||
.ToDictionary(x => x, y => y.TrackFlagId!.Split(",").Select(int.Parse).ToHashSet());
|
||
if (contentFlagIdsDict.Count == 0) return;
|
||
|
||
// 2.查轨迹标记
|
||
var flagIds = contentFlagIdsDict.SelectMany(x => x.Value).ToHashSet();
|
||
var flags = await Context.Queryable<TrackFlagEntity>()
|
||
.Where(x => flagIds.Contains(x.Id))
|
||
.Select(x => new TrackFlagVO
|
||
{
|
||
Id = x.Id,
|
||
AppKey = x.AppKey,
|
||
Code = x.Code,
|
||
Name = x.Name,
|
||
IsToMq = x.IsToMq
|
||
})
|
||
.ToListAsync();
|
||
if (flags.Count == 0) return;
|
||
|
||
// 3.填充轨迹标记
|
||
var flagDict = flags.ToDictionary(x => x.Id);
|
||
foreach (var (content, flagsIds1) in contentFlagIdsDict)
|
||
{
|
||
content.TrackFlags = flagsIds1
|
||
.Where(x => flagDict.ContainsKey(x))
|
||
.Select(x => flagDict[x])
|
||
.ToList();
|
||
|
||
// 轨迹状态为不广播,根据轨迹标记是否为广播
|
||
if (!content.IsToMq)
|
||
content.IsToMq = content.TrackFlags.Any(x => x.IsToMq);
|
||
}
|
||
}
|
||
}
|