using Fake.DependencyInjection; using Oms.Domain.CustomersAggregate.Entities; using Oms.Domain.OrdersAggregate; using Oms.Domain.OrdersAggregate.Consts; using Oms.Domain.OrdersAggregate.Entities; using Oms.Domain.OrdersAggregate.Repositories; using Oms.Domain.WarehouseAggregate.Consts; using Oms.Domain.WarehouseAggregate.Entities; using Oms.SqlSugarCore.Repositories; using SqlSugar; namespace Oms.SqlSugarCore.Orders; public class OrderRepository : OmsSqlSugarRepository, IOrderRepository, IScopedDependency { public async Task<(List Items, int Total)> GetFilteredPagedListAsync( int pageIndex, int pageSize, OrderListFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var query = ApplyListFilter(db.Queryable(), filter); var total = await query.CountAsync(cancellationToken); query = ApplyListSort(query, filter); var items = await query .ToPageListAsync(pageIndex, pageSize, cancellationToken); return (items, total); } public async Task> GetFilteredListAsync( OrderListFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); return await ApplyListSort(ApplyListFilter(db.Queryable(), filter), filter) .ToListAsync(cancellationToken); } private static ISugarQueryable ApplyListFilter(ISugarQueryable query, OrderListFilter filter) { if (filter.CustomerId.HasValue) { var customerId = filter.CustomerId.Value; query = query.Where(x => x.CustomerId == customerId); } var status = filter.Status?.Trim(); if (!string.IsNullOrWhiteSpace(status) && !string.Equals(status, "all", StringComparison.OrdinalIgnoreCase)) { query = query.Where(x => x.OrderStatus == status); } var keyword = filter.Keyword?.Trim(); if (!string.IsNullOrWhiteSpace(keyword)) { query = query.Where(o => o.ServiceNo.Contains(keyword) || (o.ClientNo ?? string.Empty).Contains(keyword) || (o.TrackNo ?? string.Empty).Contains(keyword) || (o.DownstreamServiceNo ?? string.Empty).Contains(keyword) || SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && (b.Status == OrderStockBindStatusCodes.Bound || b.Status == OrderStockBindStatusCodes.Deducted) && b.TrackingNo != null && b.TrackingNo.Contains(keyword)) .Any()); } if (filter.WarehouseId is > 0) { var warehouseId = filter.WarehouseId.Value; query = query.Where(o => o.WarehouseId == warehouseId || SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.WarehouseId == warehouseId && (b.Status == OrderStockBindStatusCodes.Bound || b.Status == OrderStockBindStatusCodes.Deducted)) .Any()); } if (filter.CreateTimeStart.HasValue) { var start = filter.CreateTimeStart.Value; query = query.Where(x => x.CreateTime >= start); } if (filter.CreateTimeEnd.HasValue) { var end = filter.CreateTimeEnd.Value; query = query.Where(x => x.CreateTime <= end); } var handleStatus = filter.HandleStatus?.Trim(); if (!string.IsNullOrWhiteSpace(handleStatus) && !string.Equals(handleStatus, "all", StringComparison.OrdinalIgnoreCase)) { if (OrderHandleStatusCodes.IsProcessed(handleStatus)) { query = query.Where(x => x.HandleStatus == OrderHandleStatusCodes.Processed); } else { query = query.Where(x => x.HandleStatus == null || x.HandleStatus == string.Empty || x.HandleStatus == OrderHandleStatusCodes.Pending); } } var orderSource = filter.OrderSource?.Trim(); if (!string.IsNullOrWhiteSpace(orderSource) && !string.Equals(orderSource, "all", StringComparison.OrdinalIgnoreCase)) { query = query.Where(x => x.OrderSource == orderSource); } var fulfillmentType = filter.FulfillmentType?.Trim(); if (!string.IsNullOrWhiteSpace(fulfillmentType) && !string.Equals(fulfillmentType, "all", StringComparison.OrdinalIgnoreCase)) { query = query.Where(x => x.FulfillmentType == fulfillmentType); } var stockBindStatus = filter.StockBindStatus?.Trim(); if (!string.IsNullOrWhiteSpace(stockBindStatus) && !string.Equals(stockBindStatus, "all", StringComparison.OrdinalIgnoreCase)) { query = ApplyStockBindStatusFilter(query, stockBindStatus); } var fulfillProgress = filter.FulfillProgress?.Trim(); if (!string.IsNullOrWhiteSpace(fulfillProgress) && !string.Equals(fulfillProgress, "all", StringComparison.OrdinalIgnoreCase)) { query = ApplyFulfillProgressFilter(query, fulfillProgress); } query = ApplyFieldFilters(query, filter.FieldFilters); query = OrderListScopeFilterApplier.Apply(query, filter); return query; } private static ISugarQueryable ApplyStockBindStatusFilter( ISugarQueryable query, string stockBindStatus) { if (string.Equals(stockBindStatus, OrderStockBindStatusCodes.Bound, StringComparison.OrdinalIgnoreCase)) { return query.Where(o => SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound) .Any() && !SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Deducted) .Any()); } if (string.Equals(stockBindStatus, OrderStockBindStatusCodes.Deducted, StringComparison.OrdinalIgnoreCase)) { return query.Where(o => SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Deducted) .Any()); } if (string.Equals(stockBindStatus, "none", StringComparison.OrdinalIgnoreCase)) { return query.Where(o => !SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && (b.Status == OrderStockBindStatusCodes.Bound || b.Status == OrderStockBindStatusCodes.Deducted)) .Any()); } return query; } private static ISugarQueryable ApplyFulfillProgressFilter( ISugarQueryable query, string fulfillProgress) { if (string.Equals(fulfillProgress, FulfillmentProgressCodes.PendingBind, StringComparison.OrdinalIgnoreCase)) { return query.Where(o => (o.FulfillmentType ?? FulfillmentTypeCodes.Direct) == FulfillmentTypeCodes.Warehouse && !SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && (b.Status == OrderStockBindStatusCodes.Bound || b.Status == OrderStockBindStatusCodes.Deducted)) .Any()); } if (string.Equals(fulfillProgress, FulfillmentProgressCodes.OutboundDone, StringComparison.OrdinalIgnoreCase)) { return query.Where(o => SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Deducted) .Any()); } if (string.Equals(fulfillProgress, FulfillmentProgressCodes.DomesticInTransit, StringComparison.OrdinalIgnoreCase)) { return ApplyStockBindStatusFilter(query, OrderStockBindStatusCodes.Bound).Where(o => (o.FulfillmentType ?? FulfillmentTypeCodes.Direct) == FulfillmentTypeCodes.Warehouse && SqlFunc.Subqueryable() .InnerJoin((b, p) => b.ParcelId == p.Id) .Where((b, p) => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound && p.LifecycleStatus == WarehouseParcelLifecycleCodes.InTransit) .Any()); } if (string.Equals(fulfillProgress, FulfillmentProgressCodes.ArrivedPendingPick, StringComparison.OrdinalIgnoreCase)) { return ApplyStockBindStatusFilter(query, OrderStockBindStatusCodes.Bound).Where(o => (o.FulfillmentType ?? FulfillmentTypeCodes.Direct) == FulfillmentTypeCodes.Warehouse && !SqlFunc.Subqueryable() .InnerJoin((b, p) => b.ParcelId == p.Id) .Where((b, p) => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound && p.LifecycleStatus == WarehouseParcelLifecycleCodes.InTransit) .Any()); } return query; } private static ISugarQueryable ApplyListSort( ISugarQueryable query, OrderListFilter filter) { var field = filter.SortField?.Trim(); var asc = string.Equals(filter.SortOrder, "asc", StringComparison.OrdinalIgnoreCase); var orderType = asc ? OrderByType.Asc : OrderByType.Desc; if (string.IsNullOrWhiteSpace(field)) { return query .OrderBy(x => x.UpdateTime, OrderByType.Desc) .OrderBy(x => x.Id, OrderByType.Desc); } if (string.Equals(field, "createTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.CreateTime, orderType); } else if (string.Equals(field, "updateTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.UpdateTime, orderType); } else if (string.Equals(field, "serviceNo", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.ServiceNo, orderType); } else if (string.Equals(field, "clientNo", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.ClientNo, orderType); } else if (string.Equals(field, "trackNo", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.TrackNo, orderType); } else if (string.Equals(field, "payableAmount", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.PayableAmount, orderType); } else if (string.Equals(field, "payTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.PayTime, orderType); } else if (string.Equals(field, "shipTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.ShipTime, orderType); } else if (string.Equals(field, "receiveTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.ReceiveTime, orderType); } else if (string.Equals(field, "signTime", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(x => x.SignTime, orderType); } else if (string.Equals(field, "primaryBinCode", StringComparison.OrdinalIgnoreCase)) { query = query.OrderBy(o => SqlFunc.Subqueryable() .InnerJoin((b, bin) => b.BinLocationId == bin.Id) .Where((b, bin) => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound) .Min((b, bin) => bin.BinCode), orderType); query = query.OrderBy(o => SqlFunc.Subqueryable() .Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound && b.TrackingNo != null && b.TrackingNo != string.Empty) .Min(b => b.TrackingNo), orderType); } else { query = query.OrderBy(x => x.UpdateTime, OrderByType.Desc); } return query.OrderBy(x => x.Id, OrderByType.Desc); } private static ISugarQueryable ApplyFieldFilters( ISugarQueryable query, List? filters) { if (filters == null || filters.Count == 0) { return query; } foreach (var filter in filters) { var field = filter.Field?.Trim() ?? string.Empty; var op = (filter.Op ?? "like").Trim().ToLowerInvariant(); var value = filter.Value?.Trim() ?? string.Empty; var values = ResolveInValues(filter); if (string.Equals(field, "serviceNo", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => x.ServiceNo.Contains(v)), (q, v) => q.Where(x => x.ServiceNo == v), (q, v) => q.Where(x => x.ServiceNo != v), (q, list) => q.Where(x => list.Contains(x.ServiceNo)), (q, list) => q.Where(x => !list.Contains(x.ServiceNo)), q => q.Where(x => x.ServiceNo == null || x.ServiceNo == string.Empty), q => q.Where(x => x.ServiceNo != null && x.ServiceNo != string.Empty)); } else if (string.Equals(field, "clientNo", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.ClientNo ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.ClientNo == v), (q, v) => q.Where(x => x.ClientNo != v), (q, list) => q.Where(x => x.ClientNo != null && list.Contains(x.ClientNo)), (q, list) => q.Where(x => x.ClientNo == null || !list.Contains(x.ClientNo)), q => q.Where(x => x.ClientNo == null || x.ClientNo == string.Empty), q => q.Where(x => x.ClientNo != null && x.ClientNo != string.Empty)); } else if (string.Equals(field, "trackNo", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.TrackNo ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.TrackNo == v), (q, v) => q.Where(x => x.TrackNo != v), (q, list) => q.Where(x => x.TrackNo != null && list.Contains(x.TrackNo)), (q, list) => q.Where(x => x.TrackNo == null || !list.Contains(x.TrackNo)), q => q.Where(x => x.TrackNo == null || x.TrackNo == string.Empty), q => q.Where(x => x.TrackNo != null && x.TrackNo != string.Empty)); } else if (string.Equals(field, "downstreamServiceNo", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.DownstreamServiceNo ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.DownstreamServiceNo == v), (q, v) => q.Where(x => x.DownstreamServiceNo != v), (q, list) => q.Where(x => x.DownstreamServiceNo != null && list.Contains(x.DownstreamServiceNo)), (q, list) => q.Where(x => x.DownstreamServiceNo == null || !list.Contains(x.DownstreamServiceNo)), q => q.Where(x => x.DownstreamServiceNo == null || x.DownstreamServiceNo == string.Empty), q => q.Where(x => x.DownstreamServiceNo != null && x.DownstreamServiceNo != string.Empty)); } else if (string.Equals(field, "orderStatus", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => x.OrderStatus.Contains(v)), (q, v) => q.Where(x => x.OrderStatus == v), (q, v) => q.Where(x => x.OrderStatus != v), (q, list) => q.Where(x => list.Contains(x.OrderStatus)), (q, list) => q.Where(x => !list.Contains(x.OrderStatus)), q => q.Where(x => x.OrderStatus == null || x.OrderStatus == string.Empty), q => q.Where(x => x.OrderStatus != null && x.OrderStatus != string.Empty)); } else if (string.Equals(field, "productCode", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.ProductCode ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.ProductCode == v), (q, v) => q.Where(x => x.ProductCode != v), (q, list) => q.Where(x => x.ProductCode != null && list.Contains(x.ProductCode)), (q, list) => q.Where(x => x.ProductCode == null || !list.Contains(x.ProductCode)), q => q.Where(x => x.ProductCode == null || x.ProductCode == string.Empty), q => q.Where(x => x.ProductCode != null && x.ProductCode != string.Empty)); } else if (string.Equals(field, "channelCode", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.ChannelCode ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.ChannelCode == v), (q, v) => q.Where(x => x.ChannelCode != v), (q, list) => q.Where(x => x.ChannelCode != null && list.Contains(x.ChannelCode)), (q, list) => q.Where(x => x.ChannelCode == null || !list.Contains(x.ChannelCode)), q => q.Where(x => x.ChannelCode == null || x.ChannelCode == string.Empty), q => q.Where(x => x.ChannelCode != null && x.ChannelCode != string.Empty)); } else if (string.Equals(field, "orderSource", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.OrderSource ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.OrderSource == v), (q, v) => q.Where(x => x.OrderSource != v), (q, list) => q.Where(x => x.OrderSource != null && list.Contains(x.OrderSource)), (q, list) => q.Where(x => x.OrderSource == null || !list.Contains(x.OrderSource)), q => q.Where(x => x.OrderSource == null || x.OrderSource == string.Empty), q => q.Where(x => x.OrderSource != null && x.OrderSource != string.Empty)); } else if (string.Equals(field, "fulfillmentType", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.FulfillmentType ?? FulfillmentTypeCodes.Direct).Contains(v)), (q, v) => q.Where(x => x.FulfillmentType == v), (q, v) => q.Where(x => x.FulfillmentType != v), (q, list) => q.Where(x => x.FulfillmentType != null && list.Contains(x.FulfillmentType)), (q, list) => q.Where(x => x.FulfillmentType == null || !list.Contains(x.FulfillmentType)), q => q.Where(x => x.FulfillmentType == null || x.FulfillmentType == string.Empty), q => q.Where(x => x.FulfillmentType != null && x.FulfillmentType != string.Empty)); } else if (string.Equals(field, "predeclareFailReason", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.PredeclareFailReason ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.PredeclareFailReason == v), (q, v) => q.Where(x => x.PredeclareFailReason != v), (q, list) => q.Where(x => x.PredeclareFailReason != null && list.Contains(x.PredeclareFailReason)), (q, list) => q.Where(x => x.PredeclareFailReason == null || !list.Contains(x.PredeclareFailReason)), q => q.Where(x => x.PredeclareFailReason == null || x.PredeclareFailReason == string.Empty), q => q.Where(x => x.PredeclareFailReason != null && x.PredeclareFailReason != string.Empty)); } else if (string.Equals(field, "allocationPendingReason", StringComparison.OrdinalIgnoreCase)) { query = ApplyStringField(query, op, value, values, (q, v) => q.Where(x => (x.AllocationPendingReason ?? string.Empty).Contains(v)), (q, v) => q.Where(x => x.AllocationPendingReason == v), (q, v) => q.Where(x => x.AllocationPendingReason != v), (q, list) => q.Where(x => x.AllocationPendingReason != null && list.Contains(x.AllocationPendingReason)), (q, list) => q.Where(x => x.AllocationPendingReason == null || !list.Contains(x.AllocationPendingReason)), q => q.Where(x => x.AllocationPendingReason == null || x.AllocationPendingReason == string.Empty), q => q.Where(x => x.AllocationPendingReason != null && x.AllocationPendingReason != string.Empty)); } else if (string.Equals(field, "isPaid", StringComparison.OrdinalIgnoreCase)) { if (byte.TryParse(value, out var paid) || TryParseBoolByte(value, out paid)) { query = op switch { "ne" => query.Where(x => x.IsPaid != paid), _ => query.Where(x => x.IsPaid == paid) }; } } else if (string.Equals(field, "payableAmount", StringComparison.OrdinalIgnoreCase)) { query = ApplyDecimalField(query, op, value, filter.Value2, (q, n) => q.Where(x => x.PayableAmount == n), (q, n) => q.Where(x => x.PayableAmount != n), (q, n) => q.Where(x => x.PayableAmount > n), (q, n) => q.Where(x => x.PayableAmount >= n), (q, n) => q.Where(x => x.PayableAmount < n), (q, n) => q.Where(x => x.PayableAmount <= n), (q, a, b) => q.Where(x => x.PayableAmount >= a && x.PayableAmount <= b), q => q.Where(x => x.PayableAmount == null), q => q.Where(x => x.PayableAmount != null)); } else if (IsDateField(field)) { query = ApplyDateField(query, field, op, value, filter.Value2); } } return query; } private static bool IsDateField(string field) => string.Equals(field, "createTime", StringComparison.OrdinalIgnoreCase) || string.Equals(field, "payTime", StringComparison.OrdinalIgnoreCase) || string.Equals(field, "shipTime", StringComparison.OrdinalIgnoreCase) || string.Equals(field, "receiveTime", StringComparison.OrdinalIgnoreCase) || string.Equals(field, "signTime", StringComparison.OrdinalIgnoreCase); private static List ResolveInValues(OrderListFieldFilter filter) { if (filter.Values is { Count: > 0 }) { return filter.Values .Where(x => !string.IsNullOrWhiteSpace(x)) .Select(x => x.Trim()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } if (string.IsNullOrWhiteSpace(filter.Value)) { return []; } return filter.Value .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) .Where(x => !string.IsNullOrWhiteSpace(x)) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); } private static bool TryParseBoolByte(string value, out byte paid) { paid = 0; if (string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "yes", StringComparison.OrdinalIgnoreCase) || value == "是") { paid = 1; return true; } if (string.Equals(value, "false", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "0", StringComparison.OrdinalIgnoreCase) || string.Equals(value, "no", StringComparison.OrdinalIgnoreCase) || value == "否") { paid = 0; return true; } return false; } private static ISugarQueryable ApplyStringField( ISugarQueryable query, string op, string value, List values, Func, string, ISugarQueryable> like, Func, string, ISugarQueryable> eq, Func, string, ISugarQueryable> ne, Func, List, ISugarQueryable> inn, Func, List, ISugarQueryable> notIn, Func, ISugarQueryable> empty, Func, ISugarQueryable> notEmpty) { return op switch { "empty" or "isnull" => empty(query), "isnotnull" => notEmpty(query), "eq" => eq(query, value), "ne" => ne(query, value), "in" when values.Count > 0 => inn(query, values), "notin" when values.Count > 0 => notIn(query, values), "like" => like(query, value), _ => like(query, value) }; } private static ISugarQueryable ApplyDecimalField( ISugarQueryable query, string op, string value, string? value2, Func, decimal, ISugarQueryable> eq, Func, decimal, ISugarQueryable> ne, Func, decimal, ISugarQueryable> gt, Func, decimal, ISugarQueryable> gte, Func, decimal, ISugarQueryable> lt, Func, decimal, ISugarQueryable> lte, Func, decimal, decimal, ISugarQueryable> between, Func, ISugarQueryable> empty, Func, ISugarQueryable> notEmpty) { if (op is "empty" or "isnull") { return empty(query); } if (op is "isnotnull") { return notEmpty(query); } if (!decimal.TryParse(value, out var n)) { return query; } if (op == "between") { if (!decimal.TryParse(value2, out var n2)) { return query; } var lo = Math.Min(n, n2); var hi = Math.Max(n, n2); return between(query, lo, hi); } return op switch { "eq" or "like" => eq(query, n), "ne" => ne(query, n), "gt" => gt(query, n), "gte" => gte(query, n), "lt" => lt(query, n), "lte" => lte(query, n), _ => eq(query, n) }; } private static ISugarQueryable ApplyDateField( ISugarQueryable query, string field, string op, string value, string? value2) { if (op is "empty" or "isnull") { return field.ToLowerInvariant() switch { "paytime" => query.Where(x => x.PayTime == null), "shiptime" => query.Where(x => x.ShipTime == null), "receivetime" => query.Where(x => x.ReceiveTime == null), "signtime" => query.Where(x => x.SignTime == null), _ => query }; } if (op is "isnotnull") { return field.ToLowerInvariant() switch { "paytime" => query.Where(x => x.PayTime != null), "shiptime" => query.Where(x => x.ShipTime != null), "receivetime" => query.Where(x => x.ReceiveTime != null), "signtime" => query.Where(x => x.SignTime != null), _ => query }; } if (!DateTime.TryParse(value, out var dt)) { return query; } if (op == "between") { if (!DateTime.TryParse(value2, out var dt2)) { return query; } var start = dt <= dt2 ? dt : dt2; var end = dt <= dt2 ? dt2 : dt; return ApplyDateCompare(query, field, "between", start, end); } return ApplyDateCompare(query, field, op, dt, null); } private static ISugarQueryable ApplyDateCompare( ISugarQueryable query, string field, string op, DateTime dt, DateTime? dt2) { var key = field.ToLowerInvariant(); if (key == "createtime") { return op switch { "eq" => query.Where(x => x.CreateTime >= dt.Date && x.CreateTime < dt.Date.AddDays(1)), "ne" => query.Where(x => x.CreateTime < dt.Date || x.CreateTime >= dt.Date.AddDays(1)), "gt" => query.Where(x => x.CreateTime > dt), "gte" => query.Where(x => x.CreateTime >= dt), "lt" => query.Where(x => x.CreateTime < dt), "lte" => query.Where(x => x.CreateTime <= dt), "between" when dt2.HasValue => query.Where(x => x.CreateTime >= dt && x.CreateTime <= dt2.Value), _ => query }; } if (key == "paytime") { return op switch { "eq" => query.Where(x => x.PayTime >= dt.Date && x.PayTime < dt.Date.AddDays(1)), "gt" => query.Where(x => x.PayTime > dt), "gte" => query.Where(x => x.PayTime >= dt), "lt" => query.Where(x => x.PayTime < dt), "lte" => query.Where(x => x.PayTime <= dt), "between" when dt2.HasValue => query.Where(x => x.PayTime >= dt && x.PayTime <= dt2.Value), _ => query }; } if (key == "shiptime") { return op switch { "eq" => query.Where(x => x.ShipTime >= dt.Date && x.ShipTime < dt.Date.AddDays(1)), "gt" => query.Where(x => x.ShipTime > dt), "gte" => query.Where(x => x.ShipTime >= dt), "lt" => query.Where(x => x.ShipTime < dt), "lte" => query.Where(x => x.ShipTime <= dt), "between" when dt2.HasValue => query.Where(x => x.ShipTime >= dt && x.ShipTime <= dt2.Value), _ => query }; } if (key == "receivetime") { return op switch { "eq" => query.Where(x => x.ReceiveTime >= dt.Date && x.ReceiveTime < dt.Date.AddDays(1)), "gt" => query.Where(x => x.ReceiveTime > dt), "gte" => query.Where(x => x.ReceiveTime >= dt), "lt" => query.Where(x => x.ReceiveTime < dt), "lte" => query.Where(x => x.ReceiveTime <= dt), "between" when dt2.HasValue => query.Where(x => x.ReceiveTime >= dt && x.ReceiveTime <= dt2.Value), _ => query }; } if (key == "signtime") { return op switch { "eq" => query.Where(x => x.SignTime >= dt.Date && x.SignTime < dt.Date.AddDays(1)), "gt" => query.Where(x => x.SignTime > dt), "gte" => query.Where(x => x.SignTime >= dt), "lt" => query.Where(x => x.SignTime < dt), "lte" => query.Where(x => x.SignTime <= dt), "between" when dt2.HasValue => query.Where(x => x.SignTime >= dt && x.SignTime <= dt2.Value), _ => query }; } return query; } public Task GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default) { return FirstOrDefaultAsync(x => x.ServiceNo == serviceNo, cancellationToken); } public Task HasServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default) { return AnyAsync(x => x.ServiceNo == serviceNo, cancellationToken); } public Task GetByTrackNoAsync(string trackNo, CancellationToken cancellationToken = default) { return FirstOrDefaultAsync(x => x.TrackNo == trackNo, cancellationToken); } public async Task GetByWaybillNoAsync(string waybillNo, CancellationToken cancellationToken = default) { var normalized = waybillNo.Trim(); if (string.IsNullOrWhiteSpace(normalized)) { return null; } var order = await GetByServiceNoAsync(normalized, cancellationToken); if (order != null) { return order; } order = await GetByTrackNoAsync(normalized, cancellationToken); if (order != null) { return order; } order = await FirstOrDefaultAsync(x => x.DownstreamServiceNo == normalized, cancellationToken); if (order != null) { return order; } return await FirstOrDefaultAsync(x => x.ClientNo == normalized, cancellationToken); } public Task GetByCustomerAndClientNoAsync(int customerId, string clientNo, CancellationToken cancellationToken = default) { return FirstOrDefaultAsync(x => x.CustomerId == customerId && x.ClientNo == clientNo, cancellationToken); } public Task GetByRequestIdAsync(string requestId, CancellationToken cancellationToken = default) { return FirstOrDefaultAsync(x => x.RequestId == requestId, cancellationToken); } public async Task> GetSendersByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Distinct().ToArray(); if (ids.Length == 0) { return []; } var db = await GetDbContextAsync(cancellationToken); return await db.Queryable() .Where(x => ids.Contains(x.OrderId)) .ToListAsync(cancellationToken); } public async Task> GetRecipientsByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Distinct().ToArray(); if (ids.Length == 0) { return []; } var db = await GetDbContextAsync(cancellationToken); return await db.Queryable() .Where(x => ids.Contains(x.OrderId)) .ToListAsync(cancellationToken); } public async Task> GetDeclarationsByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Distinct().ToArray(); if (ids.Length == 0) { return []; } var db = await GetDbContextAsync(cancellationToken); return await db.Queryable() .Where(x => ids.Contains(x.OrderId)) .ToListAsync(cancellationToken); } public async Task> GetWeightsByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Distinct().ToArray(); if (ids.Length == 0) { return []; } var db = await GetDbContextAsync(cancellationToken); return await db.Queryable() .Where(x => ids.Contains(x.OrderId)) .ToListAsync(cancellationToken); } public async Task> GetDownstreamCreateOneBundlesAsync( IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Where(x => x > 0).Distinct().ToArray(); if (ids.Length == 0) { return new Dictionary(); } var db = await GetDbContextAsync(cancellationToken); var orders = await db.Queryable() .Where(x => ids.Contains(x.Id)) .ToListAsync(cancellationToken); if (orders.Count == 0) { return new Dictionary(); } var foundIds = orders.Select(x => x.Id).ToArray(); var senders = await db.Queryable() .Where(x => foundIds.Contains(x.OrderId)) .ToListAsync(cancellationToken); var recipients = await db.Queryable() .Where(x => foundIds.Contains(x.OrderId)) .ToListAsync(cancellationToken); var declarations = await db.Queryable() .Where(x => foundIds.Contains(x.OrderId)) .ToListAsync(cancellationToken); var weights = await db.Queryable() .Where(x => foundIds.Contains(x.OrderId)) .ToListAsync(cancellationToken); var sendersByOrder = senders.GroupBy(x => x.OrderId).ToDictionary(g => g.Key, g => g.First()); var recipientsByOrder = recipients.GroupBy(x => x.OrderId).ToDictionary(g => g.Key, g => g.First()); var declarationsByOrder = declarations.GroupBy(x => x.OrderId) .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); var weightsByOrder = weights.GroupBy(x => x.OrderId) .ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList()); var result = new Dictionary(orders.Count); foreach (var order in orders) { result[order.Id] = new OrderDownstreamCreateOneBundle { Order = order, Sender = sendersByOrder.GetValueOrDefault(order.Id), Recipient = recipientsByOrder.GetValueOrDefault(order.Id), Declarations = declarationsByOrder.GetValueOrDefault(order.Id) ?? [], Weights = weightsByOrder.GetValueOrDefault(order.Id) ?? [], }; } return result; } public async Task InsertSenderAsync(OrderSender sender, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); return await db.Insertable(sender).ExecuteReturnEntityAsync(); } public async Task InsertRecipientAsync(OrderRecipient recipient, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); return await db.Insertable(recipient).ExecuteReturnEntityAsync(); } public async Task InsertDeclarationsAsync(IEnumerable declarations, CancellationToken cancellationToken = default) { var items = declarations.ToArray(); if (items.Length == 0) { return; } var db = await GetDbContextAsync(cancellationToken); await db.Insertable(items).ExecuteCommandAsync(cancellationToken); } public async Task ReplaceDeclarationsAsync(int orderId, IEnumerable declarations, CancellationToken cancellationToken = default) { var items = declarations.ToArray(); var db = await GetDbContextAsync(cancellationToken); await db.Deleteable().Where(x => x.OrderId == orderId).ExecuteCommandAsync(cancellationToken); if (items.Length > 0) { await db.Insertable(items).ExecuteCommandAsync(cancellationToken); } } public async Task InsertWeightsAsync(IEnumerable weights, CancellationToken cancellationToken = default) { var items = weights.ToArray(); if (items.Length == 0) { return; } var db = await GetDbContextAsync(cancellationToken); await db.Insertable(items).ExecuteCommandAsync(cancellationToken); } public async Task ReplaceWeightsAsync(int orderId, IEnumerable weights, CancellationToken cancellationToken = default) { var items = weights.ToArray(); var db = await GetDbContextAsync(cancellationToken); await db.Deleteable().Where(x => x.OrderId == orderId).ExecuteCommandAsync(cancellationToken); if (items.Length > 0) { await db.Insertable(items).ExecuteCommandAsync(cancellationToken); } } public async Task UpsertFeesAsync(int orderId, IEnumerable fees, CancellationToken cancellationToken = default) { var items = fees.ToArray(); if (items.Length == 0) { return; } var db = await GetDbContextAsync(cancellationToken); foreach (var fee in items) { fee.OrderId = orderId; var existingList = await db.Queryable() .Where(x => x.OrderId == orderId && !x.IsDeleted && x.Type == fee.Type && x.CostItem == fee.CostItem && x.ChargingStage == fee.ChargingStage) .Take(1) .ToListAsync(cancellationToken); var existing = existingList.FirstOrDefault(); if (existing == null) { fee.IsDeleted = false; await db.Insertable(fee).ExecuteCommandAsync(cancellationToken); continue; } existing.Amount = fee.Amount; existing.Currency = fee.Currency; existing.PriceNo = fee.PriceNo; existing.PriceEffectiveTime = fee.PriceEffectiveTime; existing.DownstreamProvider = fee.DownstreamProvider; existing.IsDeleted = false; existing.UpdateTime = DateTime.Now; await db.Updateable(existing).ExecuteCommandAsync(cancellationToken); } } public async Task UpdateLabelTemplateIdAsync(int orderId, int labelTemplateId, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); await db.Updateable() .SetColumns(x => new Order { LabelTemplateId = labelTemplateId, UpdateTime = DateTime.Now }) .Where(x => x.Id == orderId) .ExecuteCommandAsync(cancellationToken); } public async Task DeleteReceivableFeesAsync(int orderId, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); await db.Updateable() .SetColumns(x => new OrderFee { IsDeleted = true, UpdateTime = DateTime.Now }) .Where(x => x.OrderId == orderId && x.Type == OrderFeeTypeCodes.Receivable && !x.IsDeleted) .ExecuteCommandAsync(cancellationToken); } public async Task DeleteReceivableFeesByStageAsync(int orderId, string chargingStage, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); await db.Updateable() .SetColumns(x => new OrderFee { IsDeleted = true, UpdateTime = DateTime.Now }) .Where(x => x.OrderId == orderId && x.Type == OrderFeeTypeCodes.Receivable && x.ChargingStage == chargingStage && !x.IsDeleted) .ExecuteCommandAsync(cancellationToken); } public async Task DeletePayableFeesAsync(int orderId, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); await db.Updateable() .SetColumns(x => new OrderFee { IsDeleted = true, UpdateTime = DateTime.Now }) .Where(x => x.OrderId == orderId && x.Type == OrderFeeTypeCodes.Payable && !x.IsDeleted) .ExecuteCommandAsync(cancellationToken); } public async Task DeletePayableFeesByStageAsync(int orderId, string chargingStage, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); await db.Updateable() .SetColumns(x => new OrderFee { IsDeleted = true, UpdateTime = DateTime.Now }) .Where(x => x.OrderId == orderId && x.Type == OrderFeeTypeCodes.Payable && x.ChargingStage == chargingStage && !x.IsDeleted) .ExecuteCommandAsync(cancellationToken); } public async Task UpdateSenderAsync(OrderSender sender, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); sender.UpdateTime = DateTime.Now; await db.Updateable(sender).ExecuteCommandAsync(cancellationToken); } public async Task UpdateRecipientAsync(OrderRecipient recipient, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); recipient.UpdateTime = DateTime.Now; await db.Updateable(recipient).ExecuteCommandAsync(cancellationToken); } public async Task> GetFeesByOrderIdsAsync(IEnumerable orderIds, CancellationToken cancellationToken = default) { var ids = orderIds.Distinct().ToArray(); if (ids.Length == 0) { return []; } var db = await GetDbContextAsync(cancellationToken); return await db.Queryable() .Where(x => ids.Contains(x.OrderId) && !x.IsDeleted) .ToListAsync(cancellationToken); } public async Task<(List Items, int Total)> GetReceivablePayablePagedListAsync( int pageIndex, int pageSize, OrderReceivablePayableFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken); var total = await feeQuery.CountAsync(cancellationToken); var fees = await feeQuery .OrderBy(fee => fee.CreateTime, OrderByType.Desc) .OrderBy(fee => fee.Id, OrderByType.Desc) .ToPageListAsync(pageIndex, pageSize, cancellationToken); return (await MapReceivablePayableRowsAsync(db, fees, cancellationToken), total); } public async Task> GetReceivablePayableSummaryAsync( OrderReceivablePayableFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken); return await feeQuery .GroupBy(fee => new { fee.Type, fee.Currency }) .Select(g => new OrderReceivablePayableSummaryRow { FeeType = g.Type, Currency = g.Currency, TotalAmount = SqlFunc.AggregateSum(g.Amount), LineCount = SqlFunc.AggregateCount(g.Id) }) .ToListAsync(cancellationToken); } public async Task> GetReceivablePayableExportListAsync( OrderReceivablePayableFilter filter, int maxRows, CancellationToken cancellationToken = default) { if (maxRows <= 0) { return []; } var db = await GetDbContextAsync(cancellationToken); var feeQuery = await BuildReceivablePayableFeeQueryAsync(db, filter, cancellationToken); var fees = await feeQuery .OrderBy(fee => fee.CreateTime, OrderByType.Desc) .OrderBy(fee => fee.Id, OrderByType.Desc) .Take(maxRows) .ToListAsync(cancellationToken); return await MapReceivablePayableRowsAsync(db, fees, cancellationToken); } private static async Task> BuildReceivablePayableFeeQueryAsync( ISqlSugarClient db, OrderReceivablePayableFilter filter, CancellationToken cancellationToken = default) { var orderQuery = ApplyListFilter(db.Queryable(), filter); var customerCode = filter.CustomerCode?.Trim(); if (!string.IsNullOrWhiteSpace(customerCode)) { orderQuery = orderQuery.Where(o => SqlFunc.Subqueryable() .Where(c => c.Id == o.CustomerId && c.CustomerCode.Contains(customerCode)) .Any()); } orderQuery = ApplyReceivablePayableTimeFilter(orderQuery, filter); if (filter.IsPaid is 0 or 1) { var paidFlag = (byte)filter.IsPaid.Value; orderQuery = orderQuery.Where(o => o.IsPaid == paidFlag); } var orderIdsQuery = orderQuery.Select(x => x.Id); var feeQuery = db.Queryable() .Where(fee => !fee.IsDeleted) .In(fee => fee.OrderId, orderIdsQuery); var feeType = filter.FeeType?.Trim(); if (!string.IsNullOrWhiteSpace(feeType) && !string.Equals(feeType, "all", StringComparison.OrdinalIgnoreCase)) { feeQuery = feeQuery.Where(fee => fee.Type == feeType); } var costItem = filter.CostItem?.Trim(); if (!string.IsNullOrWhiteSpace(costItem)) { feeQuery = feeQuery.Where(fee => fee.CostItem == costItem || fee.CostItem.Contains(costItem)); } var chargingStage = filter.ChargingStage?.Trim(); if (!string.IsNullOrWhiteSpace(chargingStage) && !string.Equals(chargingStage, "all", StringComparison.OrdinalIgnoreCase)) { feeQuery = feeQuery.Where(fee => fee.ChargingStage == chargingStage); } if (!filter.EffectiveStageOnly) { return feeQuery; } // SqlSugar:在同一 ISugarQueryable 上 Select 后再 Where/ToPageList,会沿用投影列, // 导致 Amount/Currency 等未选字段落成默认值(金额全 0、币种空)。候选查询与最终查询拆开。 var candidates = await feeQuery .Select(f => new { f.Id, f.OrderId, f.Type, f.CostItem, f.ChargingStage }) .ToListAsync(cancellationToken); if (candidates.Count == 0) { return db.Queryable().Where(_ => false); } var winningIds = candidates .GroupBy(f => (f.OrderId, f.Type, f.CostItem)) .Select(g => g .OrderByDescending(f => GetChargingStageRank(f.ChargingStage)) .ThenByDescending(f => f.Id) .First() .Id) .ToArray(); return db.Queryable() .Where(fee => !fee.IsDeleted) .Where(f => winningIds.Contains(f.Id)); } private static int GetChargingStageRank(string? stage) => stage switch { OrderFeeChargingStageCodes.Outbound => 3, OrderFeeChargingStageCodes.Inbound => 2, OrderFeeChargingStageCodes.Forecast => 1, _ => 0 }; private static ISugarQueryable ApplyReceivablePayableTimeFilter( ISugarQueryable query, OrderReceivablePayableFilter filter) { var timeField = filter.TimeField?.Trim(); if (string.IsNullOrWhiteSpace(timeField) || string.Equals(timeField, "createTime", StringComparison.OrdinalIgnoreCase)) { return query; } if (filter.TimeStart.HasValue) { var start = filter.TimeStart.Value; query = timeField switch { "receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime >= start), "shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime >= start), "payTime" => query.Where(o => o.PayTime != null && o.PayTime >= start), _ => query }; } if (filter.TimeEnd.HasValue) { var end = filter.TimeEnd.Value; query = timeField switch { "receiveTime" => query.Where(o => o.ReceiveTime != null && o.ReceiveTime <= end), "shipTime" => query.Where(o => o.ShipTime != null && o.ShipTime <= end), "payTime" => query.Where(o => o.PayTime != null && o.PayTime <= end), _ => query }; } return query; } private static async Task> MapReceivablePayableRowsAsync( ISqlSugarClient db, List fees, CancellationToken cancellationToken) { if (fees.Count == 0) { return []; } var orderIds = fees.Select(x => x.OrderId).Distinct().ToArray(); var orders = await db.Queryable() .Where(x => orderIds.Contains(x.Id)) .ToListAsync(cancellationToken); var orderMap = orders.ToDictionary(x => x.Id); return fees .Where(fee => orderMap.ContainsKey(fee.OrderId)) .Select(fee => new OrderReceivablePayableRow { Fee = fee, Order = orderMap[fee.OrderId] }) .ToList(); } public async Task> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default) { var filter = new OrderListFilter { CustomerId = customerId }; return await GetStatusCountsByFilterAsync(filter, cancellationToken); } public async Task> GetStatusCountsByFilterAsync( OrderListFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var query = ApplyListFilter(db.Queryable(), filter); return await query .GroupBy(x => x.OrderStatus) .Select(x => new OrderStatusCountRow { Status = x.OrderStatus, Count = SqlFunc.AggregateCount(x.Id) }) .ToListAsync(cancellationToken); } public async Task> GetHandleStatusCountsByFilterAsync( OrderListFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var baseFilter = new OrderListFilter { Keyword = filter.Keyword, CreateTimeStart = filter.CreateTimeStart, CreateTimeEnd = filter.CreateTimeEnd, CustomerId = filter.CustomerId, FieldFilters = filter.FieldFilters, SortField = filter.SortField, SortOrder = filter.SortOrder, ScopeOrganizationIds = filter.ScopeOrganizationIds, ScopeSecretLevels = filter.ScopeSecretLevels, ScopeDataScope = filter.ScopeDataScope, ScopePersonalOwnerMapId = filter.ScopePersonalOwnerMapId, ScopePersonalOwnerAccount = filter.ScopePersonalOwnerAccount, ScopeOwnerMapIds = filter.ScopeOwnerMapIds, ScopeBizFilters = filter.ScopeBizFilters, ScopeCustomFilters = filter.ScopeCustomFilters }; var result = new Dictionary(StringComparer.OrdinalIgnoreCase) { [OrderStatusCodes.Problematic] = (0, 0), [OrderStatusCodes.Claimed] = (0, 0) }; foreach (var status in new[] { OrderStatusCodes.Problematic, OrderStatusCodes.Claimed }) { baseFilter.Status = status; var query = ApplyListFilter(db.Queryable(), baseFilter); var rows = await query .Select(x => x.HandleStatus) .ToListAsync(cancellationToken); var pending = rows.Count(OrderHandleStatusCodes.IsPending); var processed = rows.Count(OrderHandleStatusCodes.IsProcessed); result[status] = (pending, processed); } return result; } public async Task> GetFulfillProgressCountsByFilterAsync( OrderListFilter filter, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var baseFilter = new OrderListFilter { Keyword = filter.Keyword, CreateTimeStart = filter.CreateTimeStart, CreateTimeEnd = filter.CreateTimeEnd, CustomerId = filter.CustomerId, WarehouseId = filter.WarehouseId, FulfillmentType = filter.FulfillmentType, StockBindStatus = filter.StockBindStatus, OrderSource = filter.OrderSource, FieldFilters = filter.FieldFilters, SortField = filter.SortField, SortOrder = filter.SortOrder, ScopeOrganizationIds = filter.ScopeOrganizationIds, ScopeSecretLevels = filter.ScopeSecretLevels, ScopeDataScope = filter.ScopeDataScope, ScopePersonalOwnerMapId = filter.ScopePersonalOwnerMapId, ScopePersonalOwnerAccount = filter.ScopePersonalOwnerAccount, ScopeOwnerMapIds = filter.ScopeOwnerMapIds, ScopeBizFilters = filter.ScopeBizFilters, ScopeCustomFilters = filter.ScopeCustomFilters, }; async Task CountAsync(string? fulfillProgress) { var query = ApplyListFilter(db.Queryable(), baseFilter); if (!string.IsNullOrWhiteSpace(fulfillProgress)) { query = ApplyFulfillProgressFilter(query, fulfillProgress); } return await query.CountAsync(cancellationToken); } var result = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["all"] = await CountAsync(null), [FulfillmentProgressCodes.PendingBind] = await CountAsync(FulfillmentProgressCodes.PendingBind), [FulfillmentProgressCodes.DomesticInTransit] = await CountAsync(FulfillmentProgressCodes.DomesticInTransit), [FulfillmentProgressCodes.ArrivedPendingPick] = await CountAsync(FulfillmentProgressCodes.ArrivedPendingPick), [FulfillmentProgressCodes.OutboundDone] = await CountAsync(FulfillmentProgressCodes.OutboundDone), }; return result; } public async Task CountByFilterAndCreateTimeRangeAsync( OrderListFilter filter, DateTime startInclusive, DateTime endExclusive, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var query = ApplyListFilter(db.Queryable(), filter) .Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive) .Where(x => x.OrderStatus != OrderStatusCodes.Deleted); return await query.CountAsync(cancellationToken); } public async Task CountByFilterAndShipTimeRangeAsync( OrderListFilter filter, DateTime startInclusive, DateTime endExclusive, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var query = ApplyListFilter(db.Queryable(), filter) .Where(x => x.ShipTime != null && x.ShipTime >= startInclusive && x.ShipTime < endExclusive) .Where(x => x.OrderStatus != OrderStatusCodes.Deleted); return await query.CountAsync(cancellationToken); } public async Task SumPayableAmountByCreateTimeRangeAsync( OrderListFilter filter, DateTime startInclusive, DateTime endExclusive, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var query = ApplyListFilter(db.Queryable(), filter) .Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive) .Where(x => x.OrderStatus != OrderStatusCodes.Deleted) .Where(x => x.PayableAmount != null); return await query.SumAsync(x => x.PayableAmount!.Value); } public async Task SumFeeAmountByPayTimeRangeAsync( OrderListFilter filter, string feeType, DateTime startInclusive, DateTime endExclusive, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); var orderQuery = ApplyListFilter(db.Queryable(), filter) .Where(x => x.PayTime != null && x.PayTime >= startInclusive && x.PayTime < endExclusive) .Where(x => x.OrderStatus != OrderStatusCodes.Deleted); var orderIds = await orderQuery.Select(x => x.Id).ToListAsync(cancellationToken); if (orderIds.Count == 0) { return 0m; } return await db.Queryable() .Where(x => orderIds.Contains(x.OrderId)) .Where(x => x.Type == feeType) .SumAsync(x => x.Amount); } public async Task> GetOrdersByFilterAndCreateTimeRangeAsync( OrderListFilter filter, DateTime startInclusive, DateTime endExclusive, CancellationToken cancellationToken = default) { var db = await GetDbContextAsync(cancellationToken); return await ApplyListFilter(db.Queryable(), filter) .Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive) .Where(x => x.OrderStatus != OrderStatusCodes.Deleted) .ToListAsync(cancellationToken); } }