using Fake.FeiShu; using InterfaceForward.Domain.Shared.Helpers; using Microsoft.Extensions.Logging; using TrackCenter.Domain; using TrackCenter.Domain.Aggregates.OrderAggregate; using TrackCenter.Domain.Consts; using TrackCenter.Domain.Dtos.Order; using TrackCenter.Domain.Enums; namespace TrackCenter.Repository.Repositories; public class OrderRepository : BasicRepository, IOrderRepository, ITransientDependency { private readonly IFeiShuNotificationService _feiShuNotification; public OrderRepository(IFeiShuNotificationService feiShuNotification) { _feiShuNotification = feiShuNotification; } public async Task> GetTrackOrderPagedList(GetPageTrackOrderInputVO input) { PagedResult pageList; if (input.OrderNos.IsNullOrEmpty()) { pageList = await ApplyTrackOrderListFilters(Context.Queryable(), input) .OrderByDescending(a => a.CreateTime) .Select(TrackOrderListSelector) .ToPagedListAsync(input.PageIndex, input.PageSize); } else { var orderNos = input.OrderNos!.Distinct().ToList(); //匹配主单 var orderIds = await Context.Queryable() .Where(x => orderNos.Contains(x.OrderNo)) .Select(x => x.Id) .ToListAsync(); //匹配外部单 var orderIdsForThirdParty = await Context.Queryable() .Where(x => !string.IsNullOrWhiteSpace(x.OrderNo) && orderNos.Contains(x.OrderNo)) .Select(x => x.OrderId) .ToListAsync(); var orderHashset = orderIds.ToHashSet(); orderHashset.UnionWith(orderIdsForThirdParty); orderIds = orderHashset.ToList(); if (orderIds.Count == 0) { return new PagedResult(); } pageList = await ApplyTrackOrderListFilters( Context.Queryable().Where(a => orderIds.Contains(a.Id)), input) .OrderByDescending(a => a.CreateTime) .Select(TrackOrderListSelector) .ToPagedListAsync(input.PageIndex, input.PageSize); } pageList.Items.ForEach(x => { x.OrderTypeName = x.OrderType.GetDescription(); x.QuestionTypeName = x.QuestionType.GetDescription(); }); return pageList; } private static System.Linq.Expressions.Expression> TrackOrderListSelector => a => new TrackOrderVO { Id = a.Id, OrderNumber = a.OrderNo, OrderType = a.OrderType, QuestionType = a.QuestionType, Customer = a.Customer, NewestTrackStatus = a.LatestTrackStatus, NewestTrackContent = a.LatestTrackContent, NewestTrackEnContent = a.LatestTrackEnContent, NewestTrackPlace = a.LatestTrackPlace, NewestTrackTime = a.LatestTrackTime, NewestTrackTimeZone = a.LatestTrackTimeZone, IsFinish = a.IsCompleted, CreateTime = a.CreateTime, AppKey = a.AppKey, DestinationTimeZone = a.DestinationTimeZone, EstimatedDeliveryDate = a.EstimatedDeliveryDate, TrackNo = a.TrackNo, OtherNo = a.ThirdPartyOrderNumber, Channel = a.Channel, Product = a.ProductCode, ApiCode = a.ServiceProviderCode, CountryCode = a.ArrivalCountry, Province = a.ArrivalProvince, Postcode = a.ArrivalPostCode, City = a.ArrivalCity, Email = a.ArrivalEmail }; /// /// 表头筛选:客户/单号/产品/渠道/轨迹内容等 /// private static ISugarQueryable ApplyTrackOrderListFilters( ISugarQueryable query, GetPageTrackOrderInputVO input) { var trackNos = SplitMultiValues(input.TrackNo); var otherNos = SplitMultiValues(input.OtherNo); query = query .WhereIF(!input.AppKey.IsNullOrWhiteSpace(), a => a.AppKey == input.AppKey) .WhereIF(input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue, a => a.CreateTime >= input.CreateTimeStart && a.CreateTime <= input.CreateTimeEnd) .WhereIF(input.CreateTimeStart.HasValue && !input.CreateTimeEnd.HasValue, a => a.CreateTime >= input.CreateTimeStart) .WhereIF(!input.CreateTimeStart.HasValue && input.CreateTimeEnd.HasValue, a => a.CreateTime <= input.CreateTimeEnd) .WhereIF(!input.Customer.IsNullOrWhiteSpace(), a => a.Customer.Contains(input.Customer!)) .WhereIF(input.TrackOrderType > 0, a => a.OrderType == input.TrackOrderType) .WhereIF(input.TabType > 0, a => a.QuestionType == input.TabType) .WhereIF(input.TabType == TrackProblemType.Warn, a => !a.IsCompleted) .WhereIF(trackNos.Count == 1, a => a.TrackNo.Contains(trackNos[0])) .WhereIF(trackNos.Count > 1, a => trackNos.Contains(a.TrackNo)) .WhereIF(otherNos.Count == 1, a => a.ThirdPartyOrderNumber != null && a.ThirdPartyOrderNumber.Contains(otherNos[0])) .WhereIF(otherNos.Count > 1, a => a.ThirdPartyOrderNumber != null && otherNos.Contains(a.ThirdPartyOrderNumber)) .WhereIF(!input.Product.IsNullOrWhiteSpace(), a => a.ProductCode != null && a.ProductCode.Contains(input.Product!)) .WhereIF(!input.Channel.IsNullOrWhiteSpace(), a => a.Channel != null && a.Channel.Contains(input.Channel!)) .WhereIF(!input.ApiCode.IsNullOrWhiteSpace(), a => a.ServiceProviderCode != null && a.ServiceProviderCode.Contains(input.ApiCode!)) .WhereIF(!input.CountryCode.IsNullOrWhiteSpace(), a => a.ArrivalCountry.Contains(input.CountryCode!)) .WhereIF(!input.Province.IsNullOrWhiteSpace(), a => a.ArrivalProvince != null && a.ArrivalProvince.Contains(input.Province!)) .WhereIF(!input.City.IsNullOrWhiteSpace(), a => a.ArrivalCity.Contains(input.City!)) .WhereIF(!input.Postcode.IsNullOrWhiteSpace(), a => a.ArrivalPostCode != null && a.ArrivalPostCode.Contains(input.Postcode!)) .WhereIF(!input.Email.IsNullOrWhiteSpace(), a => a.ArrivalEmail != null && a.ArrivalEmail.Contains(input.Email!)) .WhereIF(!input.NewestTrackStatus.IsNullOrWhiteSpace(), a => a.LatestTrackStatus != null && a.LatestTrackStatus.Contains(input.NewestTrackStatus!)) .WhereIF(!input.NewestTrackContent.IsNullOrWhiteSpace(), a => a.LatestTrackContent != null && a.LatestTrackContent.Contains(input.NewestTrackContent!)) .WhereIF(!input.NewestTrackEnContent.IsNullOrWhiteSpace(), a => a.LatestTrackEnContent != null && a.LatestTrackEnContent.Contains(input.NewestTrackEnContent!)) .WhereIF(!input.NewestTrackPlace.IsNullOrWhiteSpace(), a => a.LatestTrackPlace != null && a.LatestTrackPlace.Contains(input.NewestTrackPlace!)) .WhereIF(input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue, a => a.LatestTrackTime >= input.NewestTrackTimeStart && a.LatestTrackTime <= input.NewestTrackTimeEnd) .WhereIF(input.NewestTrackTimeStart.HasValue && !input.NewestTrackTimeEnd.HasValue, a => a.LatestTrackTime >= input.NewestTrackTimeStart) .WhereIF(!input.NewestTrackTimeStart.HasValue && input.NewestTrackTimeEnd.HasValue, a => a.LatestTrackTime <= input.NewestTrackTimeEnd) .WhereIF(input.NewestTrackTimeZone.HasValue, a => a.LatestTrackTimeZone == input.NewestTrackTimeZone) .WhereIF(input.IsFinish.HasValue, a => a.IsCompleted == input.IsFinish.Value) .WhereIF(input.EstimatedDeliveryDateStart.HasValue && input.EstimatedDeliveryDateEnd.HasValue, a => a.EstimatedDeliveryDate >= input.EstimatedDeliveryDateStart && a.EstimatedDeliveryDate <= input.EstimatedDeliveryDateEnd) .WhereIF(input.EstimatedDeliveryDateStart.HasValue && !input.EstimatedDeliveryDateEnd.HasValue, a => a.EstimatedDeliveryDate >= input.EstimatedDeliveryDateStart) .WhereIF(!input.EstimatedDeliveryDateStart.HasValue && input.EstimatedDeliveryDateEnd.HasValue, a => a.EstimatedDeliveryDate <= input.EstimatedDeliveryDateEnd) .WhereIF(input.DestinationTimeZone.HasValue, a => a.DestinationTimeZone == input.DestinationTimeZone); return query; } private static List SplitMultiValues(string? raw) { if (raw.IsNullOrWhiteSpace()) return new List(); return raw! .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } public async Task<(int MainOrderId, string OrderNo, string Customer)> GetTrackOrderByProviderAndOrderNo( string serviceProviderCode, string orderNo, string? appKey = null, TrackOrderType? orderType = null) { // 拉取优先级可能配置为客户单号 / 跟踪号 / 其他单号,回传的 orderNo 不一定是主单号 var orderList = await Context.Queryable() .InnerJoin((a, b) => a.Id == b.OrderId) .Where((a, b) => b.ServiceProviderCode == serviceProviderCode) .WhereIF(!appKey.IsNullOrWhiteSpace(), a => a.AppKey == appKey) .WhereIF(orderType.HasValue, a => a.OrderType == orderType!.Value) .Where((a, b) => a.OrderNo == orderNo || a.TrackNo == orderNo || a.ThirdPartyOrderNumber == orderNo || b.OrderNo == orderNo) .Select((a, b) => new { a.Id, a.Customer, a.OrderNo, a.TrackNo, a.ThirdPartyOrderNumber, PullOrderNo = b.OrderNo }) .ToListAsync(); if (orderList.Count == 0) { return default; } // 多条时按匹配精度优先:拉取状态外部单号 > 跟踪号 > 其他单号 > 客户单号 var matchOrder = orderList.FirstOrDefault(x => x.PullOrderNo == orderNo) ?? orderList.FirstOrDefault(x => x.TrackNo == orderNo) ?? orderList.FirstOrDefault(x => x.ThirdPartyOrderNumber == orderNo) ?? orderList.First(); return (matchOrder.Id, matchOrder.OrderNo, matchOrder.Customer); } public async Task CreateOrderAsync(OrderEntity order) { using var tran = TransactionHelper.GetReadCommitted(); var orderId = await InsertReturnIdentityAsync(order); order.OrderFlights.ForEach(x => x.OrderId = orderId); await Context.Insertable(order.OrderFlights).ExecuteCommandAsync(); order.OrderPullStatus.ForEach(x => x.OrderId = orderId); await Context.Insertable(order.OrderPullStatus).ExecuteCommandAsync(); tran.Complete(); return orderId; } public async Task> CreateBatchOrderAsync(List orders) { Dictionary orderNoIdDict; using var tran = TransactionHelper.GetReadCommitted(); var orderFlights = orders.SelectMany(x => x.OrderFlights).ToList(); var orderPullStatus = orders.SelectMany(x => x.OrderPullStatus).ToList(); try { // 先批量插入 await Context.Insertable(orders).ExecuteCommandAsync(); var exp = Expressionable.Create(); foreach (var order in orders) exp.Or(x => x.OrderType == order.OrderType && x.AppKey == order.AppKey && x.OrderNo == order.OrderNo); // 查出id var queryOrders = await GetListAsync(exp.ToExpression(), x => new { x.Id, x.OrderNo }); // 填充订单关联数据中的OrderId orderNoIdDict = queryOrders.ToDictionary(x => x.OrderNo, x => x.Id); foreach (var order in orders) { var orderId = orderNoIdDict[order.OrderNo]; order.OrderFlights.ForEach(x => x.OrderId = orderId); order.OrderPullStatus.ForEach(x => x.OrderId = orderId); } await Context.Insertable(orderFlights).ExecuteCommandAsync(); await Context.Insertable(orderPullStatus).ExecuteCommandAsync(); } finally { tran.Complete(); } return orderNoIdDict; } public Task UpdateBatchIdsAsync(List ids, int newValue) { var idsString = string.Join(",", ids); return Context.Ado.ExecuteCommandAsync( $"UPDATE `t_order` SET `DeficiencyTag` = @newValue ,UpdateTime= current_timestamp WHERE `Id` IN ({idsString})", new { newValue }); } public async Task UpdateOrderAsync(OrderEntity order) { using var tran = TransactionHelper.GetReadCommitted(); // 注意:UpdateAsync(entity, cols) 的 cols 是 IgnoreColumns; // 指定要更新的列必须用 UpdateColumnAsync。 await UpdateColumnAsync(order, x => new { x.Customer, x.AppKey, x.TrackNo, x.ThirdPartyOrderNumber, x.Channel, x.ProductCode, x.ServiceProviderCode, x.ArrivalCountry, x.ArrivalProvince, x.ArrivalPostCode, x.ArrivalCity, x.ArrivalEmail, x.LeaveCountry, x.LeaveCity, x.IsTransit, x.IsCompleted, x.LatestTrackStatus, x.LatestTrackContent, x.LatestTrackEnContent, x.LatestTrackTime, x.LatestTrackTimeZone, x.LatestTrackPlace, x.EstimatedDeliveryDate, x.DestinationTimeZone, }); // 更新订单拉取状态 await Context.Deleteable().Where(x => x.OrderId == order.Id).ExecuteCommandAsync(); order.OrderPullStatus.ForEach(x => x.OrderId = order.Id); await Context.Insertable(order.OrderPullStatus).ExecuteCommandAsync(); // 更新订单航班 await Context.Deleteable().Where(x => x.OrderId == order.Id).ExecuteCommandAsync(); order.OrderFlights.ForEach(x => x.OrderId = order.Id); await Context.Insertable(order.OrderFlights).ExecuteCommandAsync(); tran.Complete(); } public async Task UpdateBatchOrderAsync(List orders) { using var tran = TransactionHelper.GetReadCommitted(); try { // 更新订单主表(与 UpdateOrderAsync 字段对齐) await UpdateRangeAsync(orders, x => new { x.Customer, x.AppKey, x.TrackNo, x.ThirdPartyOrderNumber, x.Channel, x.ProductCode, x.ServiceProviderCode, x.ArrivalCountry, x.ArrivalProvince, x.ArrivalPostCode, x.ArrivalCity, x.ArrivalEmail, x.LeaveCountry, x.LeaveCity, x.IsTransit, x.IsCompleted, x.LatestTrackStatus, x.LatestTrackContent, x.LatestTrackEnContent, x.LatestTrackTime, x.LatestTrackTimeZone, x.LatestTrackPlace, x.EstimatedDeliveryDate, x.DestinationTimeZone, }); var orderIds = orders.Select(x => x.Id).ToHashSet(); var orderFlights = orders.SelectMany(x => x.OrderFlights).ToList(); var orderPullStatus = orders.SelectMany(x => x.OrderPullStatus).ToList(); foreach (var order in orders) { order.OrderPullStatus.ForEach(x => x.OrderId = order.Id); order.OrderFlights.ForEach(x => x.OrderId = order.Id); } // 更新订单模板 await Context.Deleteable().Where(x => orderIds.Contains(x.OrderId)).ExecuteCommandAsync(); await Context.Insertable(orderPullStatus).ExecuteCommandAsync(); // 更新订单航班 await Context.Deleteable().Where(x => orderIds.Contains(x.OrderId)).ExecuteCommandAsync(); await Context.Insertable(orderFlights).ExecuteCommandAsync(); } finally { tran.Complete(); } } public async Task DeleteOrderAsync(int orderId) { using var tran = TransactionHelper.GetReadCommitted(); // 软删订单主表 await SoftDeleteAsync(x => x.Id == orderId); // 软删订单航班 // 软删订单关联关系 // 软删订单拉取状态 tran.Complete(); } /// /// 更新推送预警单 /// /// /// /// /// public async Task PushWarnOrdersAsync(string appKey, int pageSize, int pageindex) { var ordersToUpdate = await Context.Queryable() .Where(b => b.AppKey == appKey && !b.IsCompleted && b.QuestionType != TrackProblemType.Error) .Select(b => new { b.LatestPushTime, b.Id, b.QuestionType }) .ToPageListAsync(pageindex, pageSize); var latestPushTimeIds = new List(); var questionZeroIds = new List(); foreach (var item in ordersToUpdate) { var questionType = !item.LatestPushTime.HasValue || item.LatestPushTime < DateTime.Now.AddHours(-12) ? TrackProblemType.Warn : TrackProblemType.None; if (item.QuestionType == questionType) continue; if (questionType == TrackProblemType.Warn) latestPushTimeIds.Add(item.Id); else questionZeroIds.Add(item.Id); } if (latestPushTimeIds.Count > 0) UpdateOrderQuestionType(2, latestPushTimeIds); if (questionZeroIds.Count > 0) UpdateOrderQuestionType(0, questionZeroIds); return ordersToUpdate.Count; } private int UpdateOrderQuestionType(int questionType, IEnumerable ids) { var idsString = string.Join(",", ids); return Context.Ado.ExecuteCommand( $"UPDATE `t_order` SET `QuestionType` = @questionType ,UpdateTime= current_timestamp WHERE `Id` IN ({idsString})", new { questionType }); } #region 订单关联 public async Task> GetLinkedOrdersAsync(int[] orderIds) { return await Context.Queryable() .LeftJoin((a, b) => a.LinkOrderId == b.Id) .Where(a => orderIds.Contains(a.OrderId)) .Select((a, b) => new LinkedOrderVO { OwnerOrderId = a.OrderId, OrderId = b.Id, OrderNo = b.OrderNo, OrderType = b.OrderType, AppKey = b.AppKey, Customer = b.Customer }) .ToListAsync(); } public async Task> GetLinkedOrdersAsync(int orderId, string linkAppKey, TrackOrderType linkOrderType) { return await Context.Queryable() .LeftJoin((a, b) => a.LinkOrderId == b.Id) .Where((a, b) => orderId == a.OrderId && b.AppKey == linkAppKey && b.OrderType == linkOrderType) .Select((a, b) => b.OrderNo) .ToListAsync(); } public async Task> GetLinkedOrdersAsync(int orderId, string linkAppKey, TrackOrderType linkOrderType, List linkOrderNos) { return await Context.Queryable() .LeftJoin((a, b) => a.LinkOrderId == b.Id) .Where((a, b) => orderId == a.OrderId && b.AppKey == linkAppKey && b.OrderType == linkOrderType && linkOrderNos.Contains(b.OrderNo)) .Select((a, b) => a.Id) .ToListAsync(); } public async Task LinkOrderAsync(List orderLinkEntities) { await Context.Insertable(orderLinkEntities).ExecuteCommandAsync(); } public async Task UnLinkByIdsAsync(List linkIds) { await Context.Deleteable(linkIds).ExecuteCommandAsync(); } #endregion #region 订单拉取 public async Task UpdatePullStatusToSilenceAsync(List orderIds, string trackCode) { // var status = await Context.Queryable() // .Where(x => orderIds.Contains(x.OrderId) && x.StartTrackCode == trackCode) // .Where(x => x.PullStatus == TrackPullStatus.Init || x.PullStatus == TrackPullStatus.Pulling) // .Select(x => new // { // x.Id, // x.PullStatus, // x.SilenceBeforeStart, // x.SilenceAfterEnd, // x.StartTime, // x.EndTime // }) // .ToListAsync(); // // // 初始态-》静默前、拉取中-》静默后 // var changes = status.Select(x => new // { // x.Id, // PullStatus = x.PullStatus == TrackPullStatus.Init // ? x.SilenceBeforeStart > 0 // ? TrackPullStatus.SilenceBeforeStart // : TrackPullStatus.Pulling // : x.SilenceAfterEnd > 0 // ? TrackPullStatus.SilenceAfterEnd // : TrackPullStatus.Completed, // StartTime = // x.PullStatus == TrackPullStatus.Init ? DateTime.Now.AddHours(x.SilenceBeforeStart) : x.StartTime, // EndTime = x.PullStatus == TrackPullStatus.Pulling ? DateTime.Now.AddHours(x.SilenceAfterEnd) : x.EndTime // }); // await Context.Updateable(changes).ExecuteCommandAsync(); // 初始态-》静默前 await Context.Updateable() .SetColumns(x => x.PullStatus == TrackPullStatus.SilenceBeforeStart) .SetColumns(x => x.StartTime == DateTime.Now.AddHours(x.SilenceBeforeStart)) .Where(x => orderIds.Contains(x.OrderId) && x.StartTrackCode == trackCode) .Where(x => x.PullStatus == TrackPullStatus.Init) .ExecuteCommandAsync(); // 拉取中-》静默后 await Context.Updateable() .SetColumns(x => x.PullStatus == TrackPullStatus.SilenceAfterEnd) .SetColumns(x => x.EndTime == DateTime.Now.AddHours(x.SilenceAfterEnd)) // >0才有意义 .Where(x => orderIds.Contains(x.OrderId) && x.EndTrackCode == trackCode) .Where(x => x.PullStatus == TrackPullStatus.Pulling) .ExecuteCommandAsync(); } public async Task UpdatePullStatusBySilenceAsync() { var cnt1 = await Context.Updateable() .SetColumns(x => x.PullStatus == TrackPullStatus.Pulling) .Where(x => x.PullStatus == TrackPullStatus.SilenceBeforeStart && x.StartTime < DateTime.Now) .ExecuteCommandAsync(); _feiShuNotification.Enqueue($"{cnt1}条订单的拉取状态由{nameof(TrackPullStatus.SilenceBeforeStart)}更新为{nameof(TrackPullStatus.Pulling)}", LogLevel.Error); var cnt2 = await Context.Updateable() .SetColumns(x => x.PullStatus == TrackPullStatus.Completed) .Where(x => x.PullStatus == TrackPullStatus.SilenceAfterEnd && x.EndTime < DateTime.Now) .ExecuteCommandAsync(); _feiShuNotification.Enqueue($"{cnt2}条订单的拉取状态由{nameof(TrackPullStatus.SilenceAfterEnd)}更新为{nameof(TrackPullStatus.Completed)}", LogLevel.Error); return cnt1 + cnt2; } public async Task ClearPullStatusAsync() { var time = DateTime.Now.Subtract(TimeSpan.FromDays(30)); return await Context.Deleteable().Where(x => x.PullStatus == TrackPullStatus.Completed || x.StartTime < time) .ExecuteCommandAsync(); } public async Task> FindOrderPullConfigsAsync( string appKey, TrackOrderType orderType, List orderNos) { if (orderNos.Count == 0) { return []; } return await Context.Queryable() .InnerJoin((order, pullStatus) => order.Id == pullStatus.OrderId) .Where((order, pullStatus) => order.AppKey == appKey && order.OrderType == orderType && orderNos.Contains(order.OrderNo)) .Select((order, pullStatus) => new FindOrderPullConfigsOutputVO { OrderNo = order.OrderNo, ServiceCode = pullStatus.ServiceProviderCode, PullOrderNo = pullStatus.OrderNo, StartCode = pullStatus.StartTrackCode, EndCode = pullStatus.EndTrackCode, SilenceTime = pullStatus.SilenceBeforeStart, StopTime = pullStatus.SilenceAfterEnd, PullStatus = pullStatus.PullStatus, StartTime = pullStatus.StartTime, EndTime = pullStatus.EndTime }) .ToListAsync(); } public async Task UpdateOrderPullConfigForServiceProviderAsync( UpdateOrderPullConfigsInputVo inputVo) { // tips:如此更新,影响范围是否可控?状态是否正确? return await Context.Updateable() .SetColumns(x => x.ServiceProviderCode == inputVo.ServiceCode) .SetColumns(x => x.StartTrackCode == inputVo.StartCode) .SetColumns(x => x.EndTrackCode == inputVo.EndCode) .SetColumns(x => x.SilenceBeforeStart == inputVo.SilenceTime) .SetColumns(x => x.SilenceAfterEnd == inputVo.StopTime) // 直接将订单拉取状态更新为拉取中,不再考虑性能 .SetColumns(x => x.StartTime == DateTime.Now) .SetColumns(x => x.EndTime == GlobalConst.MaxDateTime) .SetColumns(x => x.PullStatus == TrackPullStatus.Pulling) .Where(x => inputVo.OrderIds.Contains(x.OrderId)) .ExecuteCommandAsync(); } #endregion }