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;
///
/// 提供服务相关查询
///
public class ProvideQueries : SqlSugarRootlessRepository, ITransientDependency
{
public ISqlSugarClient Context => GetDbContextAsync().Result;
public async Task> MapTrackCodesAsync(string upAppKey, string appKey, params string[] trackCodes)
{
// 根据上游code得到下游code
return await Context.Queryable()
.Where(x => x.AppKey == appKey && x.MapAppKey == upAppKey &&
trackCodes.Contains(x.MapTrackContentCode))
.Select(x => x.TrackContentCode)
.ToListAsync();
}
public async Task> GetTrackCodeMapsAsync(string upAppKey, string appKey,
params string[] trackCodes)
{
// 根据上游code得到下游code
return await Context.Queryable()
.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> FindOrderWithServiceProviderAsync(string appKey,
List orderNos)
{
var orderRows = await Context.Queryable()
.InnerJoin((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()
.InnerJoin((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();
}
///
/// 按拉取优先级配置的单号类型解析下游拉取主单号。
///
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,
};
}
///
/// 手工刷新诊断:区分可刷新订单与不可刷新原因
///
public async Task<(List CanRefresh, List SkipReasons)>
DiagnoseManualRefreshAsync(string appKey, List orderNos)
{
var now = DateTime.Now;
var canRefresh = await FindOrderWithServiceProviderAsync(appKey, orderNos);
var eligibleOrderNos = await Context.Queryable()
.InnerJoin((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()
.LeftJoin((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();
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> FindOrderWithServiceProviderAsync(
TrackPullConfigVo config,
int pageIndex, int pageSize = 1000)
{
var rows = await Context.Queryable()
.InnerJoin((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 GetTrackContentAsync(string appKey, string trackCode)
{
var content = await Context.Queryable()
.LeftJoin((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(1)
{
content
});
return content;
}
public async Task> GetTrackContentsAsync(string[] appKeys, string[] trackCodes)
{
var contents = await Context.Queryable()
.LeftJoin((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> GetTrackContentAsync(HashSet contentIds)
{
var contents = await Context.Queryable()
.LeftJoin((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 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()
.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);
}
}
}