admin/src/Oms.SqlSugarCore/Orders/OrderRepository.cs
zzh 4b237c1374
All checks were successful
Build and Deploy / deploy (push) Successful in 1m12s
fix: 代理运单列表按名下客户过滤
门户可见范围改用客户当前 AgentId;返佣仍认订单快照 AgentId。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-30 10:52:16 +08:00

1698 lines
71 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<Order>, IOrderRepository, IScopedDependency
{
public async Task<(List<Order> Items, int Total)> GetFilteredPagedListAsync(
int pageIndex,
int pageSize,
OrderListFilter filter,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
var query = ApplyListFilter(db.Queryable<Order>(), filter);
var total = await query.CountAsync(cancellationToken);
query = ApplyListSort(query, filter);
// 列表仅需 HasAllocationTrace 布尔语义,勿拉取大字段 AllocationTraceJson
var items = await query
.IgnoreColumns(x => x.AllocationTraceJson)
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
return (items, total);
}
public async Task<List<Order>> GetFilteredListAsync(
OrderListFilter filter,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await ApplyListSort(ApplyListFilter(db.Queryable<Order>(), filter), filter)
.ToListAsync(cancellationToken);
}
private static ISugarQueryable<Order> ApplyListFilter(ISugarQueryable<Order> query, OrderListFilter filter)
{
if (filter.CustomerId.HasValue)
{
var customerId = filter.CustomerId.Value;
query = query.Where(x => x.CustomerId == customerId);
}
if (filter.AgentId.HasValue)
{
var agentId = filter.AgentId.Value;
query = query.Where(x => x.AgentId == agentId);
}
// 名下客户可见:本客户 AgentId,或子客户的主客户 AgentId
if (filter.OwnedByAgentId.HasValue)
{
var ownedByAgentId = filter.OwnedByAgentId.Value;
query = query.Where(o =>
SqlFunc.Subqueryable<Customer>()
.Where(c => c.Id == o.CustomerId && c.AgentId == ownedByAgentId)
.Any()
|| SqlFunc.Subqueryable<Customer>()
.Where(c => c.Id == o.CustomerId
&& c.ParentCustomerId != null
&& SqlFunc.Subqueryable<Customer>()
.Where(m => m.Id == c.ParentCustomerId && m.AgentId == ownedByAgentId)
.Any())
.Any());
}
var status = filter.Status?.Trim();
var isDeletedTab = string.Equals(status, OrderStatusCodes.Deleted, StringComparison.OrdinalIgnoreCase);
if (isDeletedTab)
{
// 历史删除曾同时软删(IsDeleted=true),需清过滤才能在「已删除」看到
query = query.ClearFilter().Where(x => x.OrderStatus == OrderStatusCodes.Deleted);
}
else if (string.Equals(status, "all", StringComparison.OrdinalIgnoreCase))
{
// 列表「全部」不含已删除;status 为空时留给 status-counts 聚合(需含 deleted 桶)
query = query.Where(x => x.OrderStatus != OrderStatusCodes.Deleted);
}
else if (!string.IsNullOrWhiteSpace(status))
{
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<OrderStockBind>()
.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<OrderStockBind>()
.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<Order> ApplyStockBindStatusFilter(
ISugarQueryable<Order> query,
string stockBindStatus)
{
if (string.Equals(stockBindStatus, OrderStockBindStatusCodes.Bound, StringComparison.OrdinalIgnoreCase))
{
return query.Where(o =>
SqlFunc.Subqueryable<OrderStockBind>()
.Where(b => b.OrderId == o.Id && !b.IsDeleted && b.Status == OrderStockBindStatusCodes.Bound)
.Any()
&& !SqlFunc.Subqueryable<OrderStockBind>()
.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<OrderStockBind>()
.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<OrderStockBind>()
.Where(b => b.OrderId == o.Id && !b.IsDeleted
&& (b.Status == OrderStockBindStatusCodes.Bound || b.Status == OrderStockBindStatusCodes.Deducted))
.Any());
}
return query;
}
private static ISugarQueryable<Order> ApplyFulfillProgressFilter(
ISugarQueryable<Order> query,
string fulfillProgress)
{
if (string.Equals(fulfillProgress, FulfillmentProgressCodes.PendingBind, StringComparison.OrdinalIgnoreCase))
{
return query.Where(o =>
(o.FulfillmentType ?? FulfillmentTypeCodes.Direct) == FulfillmentTypeCodes.Warehouse
&& !SqlFunc.Subqueryable<OrderStockBind>()
.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<OrderStockBind>()
.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<OrderStockBind>()
.InnerJoin<CParcel>((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<OrderStockBind>()
.InnerJoin<CParcel>((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<Order> ApplyListSort(
ISugarQueryable<Order> 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<OrderStockBind>()
.InnerJoin<CBinLocation>((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<OrderStockBind>()
.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<Order> ApplyFieldFilters(
ISugarQueryable<Order> query,
List<OrderListFieldFilter>? 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)
|| string.Equals(field, "isIntercept", StringComparison.OrdinalIgnoreCase)
|| string.Equals(field, "isRemote", StringComparison.OrdinalIgnoreCase)
|| string.Equals(field, "isCod", StringComparison.OrdinalIgnoreCase))
{
query = ApplyByteFlagField(query, field, op, value, values);
}
else if (string.Equals(field, "handleStatus", StringComparison.OrdinalIgnoreCase))
{
query = ApplyStringField(query, op, value, values,
(q, v) => q.Where(x => (x.HandleStatus ?? string.Empty).Contains(v)),
(q, v) => q.Where(x => x.HandleStatus == v),
(q, v) => q.Where(x => x.HandleStatus != v),
(q, list) => q.Where(x => x.HandleStatus != null && list.Contains(x.HandleStatus)),
(q, list) => q.Where(x => x.HandleStatus == null || !list.Contains(x.HandleStatus)),
q => q.Where(x => x.HandleStatus == null || x.HandleStatus == string.Empty),
q => q.Where(x => x.HandleStatus != null && x.HandleStatus != string.Empty));
}
else if (string.Equals(field, "previousOrderStatus", StringComparison.OrdinalIgnoreCase))
{
query = ApplyStringField(query, op, value, values,
(q, v) => q.Where(x => (x.PreviousOrderStatus ?? string.Empty).Contains(v)),
(q, v) => q.Where(x => x.PreviousOrderStatus == v),
(q, v) => q.Where(x => x.PreviousOrderStatus != v),
(q, list) => q.Where(x => x.PreviousOrderStatus != null && list.Contains(x.PreviousOrderStatus)),
(q, list) => q.Where(x => x.PreviousOrderStatus == null || !list.Contains(x.PreviousOrderStatus)),
q => q.Where(x => x.PreviousOrderStatus == null || x.PreviousOrderStatus == string.Empty),
q => q.Where(x => x.PreviousOrderStatus != null && x.PreviousOrderStatus != string.Empty));
}
else if (string.Equals(field, "stockBindStatus", StringComparison.OrdinalIgnoreCase))
{
var statusValue = values.Count == 1 ? values[0] : value;
if (!string.IsNullOrWhiteSpace(statusValue)
&& (op is "eq" or "in" or "like" || string.IsNullOrWhiteSpace(op)))
{
query = ApplyStockBindStatusFilter(query, statusValue);
}
}
else if (string.Equals(field, "fulfillProgress", StringComparison.OrdinalIgnoreCase))
{
var progressValue = values.Count == 1 ? values[0] : value;
if (!string.IsNullOrWhiteSpace(progressValue)
&& (op is "eq" or "in" or "like" || string.IsNullOrWhiteSpace(op)))
{
query = ApplyFulfillProgressFilter(query, progressValue);
}
}
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<string> 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 List<byte> ResolveBoolBytes(string value, List<string> values)
{
var raw = values.Count > 0
? values
: string.IsNullOrWhiteSpace(value)
? []
: value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
var flags = new List<byte>();
foreach (var item in raw)
{
if (byte.TryParse(item, out var flag) || TryParseBoolByte(item, out flag))
{
flags.Add(flag);
}
}
return flags.Distinct().ToList();
}
/// <summary>
/// 字节布尔标志筛选(0/1),支持 eq / ne / in / notIn。
/// </summary>
private static ISugarQueryable<Order> ApplyByteFlagField(
ISugarQueryable<Order> query,
string field,
string op,
string value,
List<string> values)
{
var flags = ResolveBoolBytes(value, values);
if (flags.Count == 0)
{
return query;
}
var negate = op is "ne" or "notIn";
var multi = op is "in" or "notIn" || flags.Count > 1;
if (string.Equals(field, "isPaid", StringComparison.OrdinalIgnoreCase))
{
return negate
? query.Where(x => !flags.Contains(x.IsPaid))
: multi
? query.Where(x => flags.Contains(x.IsPaid))
: query.Where(x => x.IsPaid == flags[0]);
}
if (string.Equals(field, "isIntercept", StringComparison.OrdinalIgnoreCase))
{
return negate
? query.Where(x => !flags.Contains(x.IsIntercept))
: multi
? query.Where(x => flags.Contains(x.IsIntercept))
: query.Where(x => x.IsIntercept == flags[0]);
}
if (string.Equals(field, "isRemote", StringComparison.OrdinalIgnoreCase))
{
return negate
? query.Where(x => !flags.Contains(x.IsRemote))
: multi
? query.Where(x => flags.Contains(x.IsRemote))
: query.Where(x => x.IsRemote == flags[0]);
}
if (string.Equals(field, "isCod", StringComparison.OrdinalIgnoreCase))
{
return negate
? query.Where(x => !flags.Contains(x.IsCod))
: multi
? query.Where(x => flags.Contains(x.IsCod))
: query.Where(x => x.IsCod == flags[0]);
}
return query;
}
private static ISugarQueryable<Order> ApplyStringField(
ISugarQueryable<Order> query,
string op,
string value,
List<string> values,
Func<ISugarQueryable<Order>, string, ISugarQueryable<Order>> like,
Func<ISugarQueryable<Order>, string, ISugarQueryable<Order>> eq,
Func<ISugarQueryable<Order>, string, ISugarQueryable<Order>> ne,
Func<ISugarQueryable<Order>, List<string>, ISugarQueryable<Order>> inn,
Func<ISugarQueryable<Order>, List<string>, ISugarQueryable<Order>> notIn,
Func<ISugarQueryable<Order>, ISugarQueryable<Order>> empty,
Func<ISugarQueryable<Order>, ISugarQueryable<Order>> 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<Order> ApplyDecimalField(
ISugarQueryable<Order> query,
string op,
string value,
string? value2,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> eq,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> ne,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> gt,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> gte,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> lt,
Func<ISugarQueryable<Order>, decimal, ISugarQueryable<Order>> lte,
Func<ISugarQueryable<Order>, decimal, decimal, ISugarQueryable<Order>> between,
Func<ISugarQueryable<Order>, ISugarQueryable<Order>> empty,
Func<ISugarQueryable<Order>, ISugarQueryable<Order>> 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<Order> ApplyDateField(
ISugarQueryable<Order> 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<Order> ApplyDateCompare(
ISugarQueryable<Order> 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<Order?> GetByServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.ServiceNo == serviceNo, cancellationToken);
}
public Task<bool> HasServiceNoAsync(string serviceNo, CancellationToken cancellationToken = default)
{
return AnyAsync(x => x.ServiceNo == serviceNo, cancellationToken);
}
public Task<Order?> GetByTrackNoAsync(string trackNo, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.TrackNo == trackNo, cancellationToken);
}
public async Task<Order?> 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<Order?> GetByCustomerAndClientNoAsync(int customerId, string clientNo, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.CustomerId == customerId && x.ClientNo == clientNo, cancellationToken);
}
public Task<Order?> GetByRequestIdAsync(string requestId, CancellationToken cancellationToken = default)
{
return FirstOrDefaultAsync(x => x.RequestId == requestId, cancellationToken);
}
public async Task<List<OrderSender>> GetSendersByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return [];
}
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<OrderSender>()
.Where(x => ids.Contains(x.OrderId))
.ToListAsync(cancellationToken);
}
public async Task<List<OrderRecipient>> GetRecipientsByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return [];
}
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<OrderRecipient>()
.Where(x => ids.Contains(x.OrderId))
.ToListAsync(cancellationToken);
}
public async Task<List<OrderDeclaration>> GetDeclarationsByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return [];
}
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<OrderDeclaration>()
.Where(x => ids.Contains(x.OrderId))
.ToListAsync(cancellationToken);
}
public async Task<List<OrderWeight>> GetWeightsByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return [];
}
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<OrderWeight>()
.Where(x => ids.Contains(x.OrderId))
.ToListAsync(cancellationToken);
}
public async Task<Dictionary<int, OrderDownstreamCreateOneBundle>> GetDownstreamCreateOneBundlesAsync(
IEnumerable<int> orderIds,
CancellationToken cancellationToken = default)
{
var ids = orderIds.Where(x => x > 0).Distinct().ToArray();
if (ids.Length == 0)
{
return new Dictionary<int, OrderDownstreamCreateOneBundle>();
}
var db = await GetDbContextAsync(cancellationToken);
var orders = await db.Queryable<Order>()
.Where(x => ids.Contains(x.Id))
.ToListAsync(cancellationToken);
if (orders.Count == 0)
{
return new Dictionary<int, OrderDownstreamCreateOneBundle>();
}
var foundIds = orders.Select(x => x.Id).ToArray();
var senders = await db.Queryable<OrderSender>()
.Where(x => foundIds.Contains(x.OrderId))
.ToListAsync(cancellationToken);
var recipients = await db.Queryable<OrderRecipient>()
.Where(x => foundIds.Contains(x.OrderId))
.ToListAsync(cancellationToken);
var declarations = await db.Queryable<OrderDeclaration>()
.Where(x => foundIds.Contains(x.OrderId))
.ToListAsync(cancellationToken);
var weights = await db.Queryable<OrderWeight>()
.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<OrderDeclaration>)g.ToList());
var weightsByOrder = weights.GroupBy(x => x.OrderId)
.ToDictionary(g => g.Key, g => (IReadOnlyList<OrderWeight>)g.ToList());
var result = new Dictionary<int, OrderDownstreamCreateOneBundle>(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<OrderSender> InsertSenderAsync(OrderSender sender, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Insertable(sender).ExecuteReturnEntityAsync();
}
public async Task<OrderRecipient> InsertRecipientAsync(OrderRecipient recipient, CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await db.Insertable(recipient).ExecuteReturnEntityAsync();
}
public async Task InsertDeclarationsAsync(IEnumerable<OrderDeclaration> 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<OrderDeclaration> declarations, CancellationToken cancellationToken = default)
{
var items = declarations.ToArray();
var db = await GetDbContextAsync(cancellationToken);
await db.Deleteable<OrderDeclaration>().Where(x => x.OrderId == orderId).ExecuteCommandAsync(cancellationToken);
if (items.Length > 0)
{
await db.Insertable(items).ExecuteCommandAsync(cancellationToken);
}
}
public async Task InsertWeightsAsync(IEnumerable<OrderWeight> 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<OrderWeight> weights, CancellationToken cancellationToken = default)
{
var items = weights.ToArray();
var db = await GetDbContextAsync(cancellationToken);
await db.Deleteable<OrderWeight>().Where(x => x.OrderId == orderId).ExecuteCommandAsync(cancellationToken);
if (items.Length > 0)
{
await db.Insertable(items).ExecuteCommandAsync(cancellationToken);
}
}
public async Task UpsertFeesAsync(int orderId, IEnumerable<OrderFee> 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<OrderFee>()
.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<Order>()
.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<OrderFee>()
.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<OrderFee>()
.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<OrderFee>()
.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<OrderFee>()
.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<List<OrderFee>> GetFeesByOrderIdsAsync(IEnumerable<int> orderIds, CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return [];
}
var db = await GetDbContextAsync(cancellationToken);
return await db.Queryable<OrderFee>()
.Where(x => ids.Contains(x.OrderId) && !x.IsDeleted)
.ToListAsync(cancellationToken);
}
/// <summary>
/// 列表页:按订单聚合应收金额合计(不加载费用明细行)。
/// </summary>
public async Task<Dictionary<int, decimal>> GetReceivableSumByOrderIdsAsync(
IEnumerable<int> orderIds,
CancellationToken cancellationToken = default)
{
var ids = orderIds.Distinct().ToArray();
if (ids.Length == 0)
{
return new Dictionary<int, decimal>();
}
var db = await GetDbContextAsync(cancellationToken);
var rows = await db.Queryable<OrderFee>()
.Where(x => ids.Contains(x.OrderId)
&& !x.IsDeleted
&& x.Type == OrderFeeTypeCodes.Receivable)
.GroupBy(x => x.OrderId)
.Select(x => new
{
OrderId = x.OrderId,
Amount = SqlFunc.AggregateSum(x.Amount),
})
.ToListAsync(cancellationToken);
return rows.ToDictionary(x => x.OrderId, x => x.Amount);
}
public async Task<(List<OrderReceivablePayableRow> 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<List<OrderReceivablePayableSummaryRow>> 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<List<OrderReceivablePayableRow>> 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<ISugarQueryable<OrderFee>> BuildReceivablePayableFeeQueryAsync(
ISqlSugarClient db,
OrderReceivablePayableFilter filter,
CancellationToken cancellationToken = default)
{
var orderQuery = ApplyListFilter(db.Queryable<Order>(), filter);
var customerCode = filter.CustomerCode?.Trim();
if (!string.IsNullOrWhiteSpace(customerCode))
{
orderQuery = orderQuery.Where(o =>
SqlFunc.Subqueryable<Customer>()
.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<OrderFee>()
.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<OrderFee>().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<OrderFee>()
.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<Order> ApplyReceivablePayableTimeFilter(
ISugarQueryable<Order> 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<List<OrderReceivablePayableRow>> MapReceivablePayableRowsAsync(
ISqlSugarClient db,
List<OrderFee> fees,
CancellationToken cancellationToken)
{
if (fees.Count == 0)
{
return [];
}
var orderIds = fees.Select(x => x.OrderId).Distinct().ToArray();
var orders = await db.Queryable<Order>()
.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<List<OrderStatusCountRow>> GetStatusCountsAsync(int? customerId, CancellationToken cancellationToken = default)
{
var filter = new OrderListFilter { CustomerId = customerId };
return await GetStatusCountsByFilterAsync(filter, cancellationToken);
}
public async Task<List<OrderStatusCountRow>> GetStatusCountsByFilterAsync(
OrderListFilter filter,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
// 含历史「状态=deleted 且已软删」的单据,保证已删除 Tab 角标可见
var query = ApplyListFilter(
db.Queryable<Order>()
.ClearFilter()
.Where(x => !x.IsDeleted || x.OrderStatus == OrderStatusCodes.Deleted),
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<Dictionary<string, (int Pending, int Processed)>> 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,
AgentId = filter.AgentId,
OwnedByAgentId = filter.OwnedByAgentId,
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<string, (int Pending, int Processed)>(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<Order>(), 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<Dictionary<string, int>> 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,
AgentId = filter.AgentId,
OwnedByAgentId = filter.OwnedByAgentId,
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<int> CountAsync(string? fulfillProgress)
{
var query = ApplyListFilter(db.Queryable<Order>(), baseFilter);
if (!string.IsNullOrWhiteSpace(fulfillProgress))
{
query = ApplyFulfillProgressFilter(query, fulfillProgress);
}
return await query.CountAsync(cancellationToken);
}
var result = new Dictionary<string, int>(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<int> CountByFilterAndCreateTimeRangeAsync(
OrderListFilter filter,
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
var query = ApplyListFilter(db.Queryable<Order>(), filter)
.Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive)
.Where(x => x.OrderStatus != OrderStatusCodes.Deleted);
return await query.CountAsync(cancellationToken);
}
public async Task<int> CountByFilterAndShipTimeRangeAsync(
OrderListFilter filter,
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
var query = ApplyListFilter(db.Queryable<Order>(), 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<decimal> SumPayableAmountByCreateTimeRangeAsync(
OrderListFilter filter,
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
var query = ApplyListFilter(db.Queryable<Order>(), 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<decimal> SumFeeAmountByPayTimeRangeAsync(
OrderListFilter filter,
string feeType,
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
var orderQuery = ApplyListFilter(db.Queryable<Order>(), 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<OrderFee>()
.Where(x => orderIds.Contains(x.OrderId))
.Where(x => x.Type == feeType)
.SumAsync(x => x.Amount);
}
public async Task<List<Order>> GetOrdersByFilterAndCreateTimeRangeAsync(
OrderListFilter filter,
DateTime startInclusive,
DateTime endExclusive,
CancellationToken cancellationToken = default)
{
var db = await GetDbContextAsync(cancellationToken);
return await ApplyListFilter(db.Queryable<Order>(), filter)
.Where(x => x.CreateTime >= startInclusive && x.CreateTime < endExclusive)
.Where(x => x.OrderStatus != OrderStatusCodes.Deleted)
.ToListAsync(cancellationToken);
}
}