All checks were successful
Build and Deploy / deploy (push) Successful in 1m2s
SqlSugar 在同一查询上 Select 后再分页会沿用投影列,导致 Amount/Currency 丢失;候选筛选与最终全字段查询拆开。 Co-authored-by: Cursor <cursoragent@cursor.com>
1513 lines
63 KiB
C#
1513 lines
63 KiB
C#
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);
|
||
}
|
||
}
|