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