admin/src/Oms.SqlSugarCore/Orders/OrderRepository.cs
zzh 66d383160f
All checks were successful
Build and Deploy / deploy (push) Successful in 1m2s
fix: 应收应付「仅当前生效阶段」列表金额为 0
SqlSugar 在同一查询上 Select 后再分页会沿用投影列,导致 Amount/Currency 丢失;候选筛选与最终全字段查询拆开。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-15 20:11:46 +08:00

1513 lines
63 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);
var items = await query
.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);
}
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<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))
{
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<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 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);
}
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);
var query = ApplyListFilter(db.Queryable<Order>(), 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,
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,
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);
}
}