feat: 订单列表表头筛选、远程排序与默认90天时间窗护栏
支持 filters[]/whitelist 排序,导出行数与批量 IN 上限,无时间窗时兜底近90天。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
238eb5d817
commit
a55668ec33
@ -24,4 +24,24 @@ public class CustomerAccountFlowPaginatedInput
|
||||
public DateTime? StartTime { get; set; }
|
||||
|
||||
public DateTime? EndTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动金额下限(含)。
|
||||
/// </summary>
|
||||
public decimal? AmountMin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 变动金额上限(含)。
|
||||
/// </summary>
|
||||
public decimal? AmountMax { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段:createTime / amount。
|
||||
/// </summary>
|
||||
public string? SortField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// asc / desc。
|
||||
/// </summary>
|
||||
public string? SortOrder { get; set; }
|
||||
}
|
||||
|
||||
@ -41,4 +41,14 @@ public class CustomerPaginatedInput
|
||||
/// 所属顾问筛选。
|
||||
/// </summary>
|
||||
public string? CustomerService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段:createTime / updateTime / customerCode / customerName / balance。
|
||||
/// </summary>
|
||||
public string? SortField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// asc / desc。
|
||||
/// </summary>
|
||||
public string? SortOrder { get; set; }
|
||||
}
|
||||
|
||||
@ -34,4 +34,19 @@ public class OrderExportInput
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("createTimeEnd")]
|
||||
public DateTime? CreateTimeEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表头字段级筛选(白名单)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("filters")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("filters")]
|
||||
public List<OrderListFieldFilterDto>? Filters { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("sortField")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("sortField")]
|
||||
public string? SortField { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("sortOrder")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("sortOrder")]
|
||||
public string? SortOrder { get; set; }
|
||||
}
|
||||
|
||||
36
src/Oms.Application/Dtos/Orders/OrderListFieldFilterDto.cs
Normal file
36
src/Oms.Application/Dtos/Orders/OrderListFieldFilterDto.cs
Normal file
@ -0,0 +1,36 @@
|
||||
namespace Oms.Application.Dtos.Orders;
|
||||
|
||||
/// <summary>
|
||||
/// 订单列表字段级筛选(表头 filters[],白名单字段 + DataPerm 风格算子)。
|
||||
/// </summary>
|
||||
public class OrderListFieldFilterDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段白名单见 OrderService.NormalizeOrderFieldFilters。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("field")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("field")]
|
||||
public string Field { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// like / eq / ne / in / notIn / gt / gte / lt / lte / between / empty / isNull / isNotNull。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("op")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("op")]
|
||||
public string Op { get; set; } = "like";
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("value")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("value")]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("value2")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("value2")]
|
||||
public string? Value2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IN / NOT IN 多值(优先于 value 逗号拆分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("values")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("values")]
|
||||
public List<string>? Values { get; set; }
|
||||
}
|
||||
@ -62,4 +62,25 @@ public class OrderPaginatedInput
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("createTimeEnd")]
|
||||
public DateTime? CreateTimeEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表头字段级筛选(白名单);与 keyword 并存时 AND。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("filters")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("filters")]
|
||||
public List<OrderListFieldFilterDto>? Filters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段白名单:createTime / updateTime / serviceNo / clientNo / trackNo / payableAmount / payTime / shipTime / receiveTime / signTime。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("sortField")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("sortField")]
|
||||
public string? SortField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// asc / desc。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("sortOrder")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("sortOrder")]
|
||||
public string? SortOrder { get; set; }
|
||||
}
|
||||
|
||||
33
src/Oms.Application/Dtos/Shared/ListFieldFilterDto.cs
Normal file
33
src/Oms.Application/Dtos/Shared/ListFieldFilterDto.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace Oms.Application.Dtos.Shared;
|
||||
|
||||
/// <summary>
|
||||
/// 列表字段级筛选(表头 filters[],白名单字段由各接口自行校验)。
|
||||
/// </summary>
|
||||
public class ListFieldFilterDto
|
||||
{
|
||||
[Newtonsoft.Json.JsonProperty("field")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("field")]
|
||||
public string Field { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// like / eq / ne / in / notIn / gt / gte / lt / lte / between / empty / isNull / isNotNull。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("op")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("op")]
|
||||
public string Op { get; set; } = "like";
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("value")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("value")]
|
||||
public string? Value { get; set; }
|
||||
|
||||
[Newtonsoft.Json.JsonProperty("value2")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("value2")]
|
||||
public string? Value2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// IN / NOT IN 多值(优先于 value 逗号拆分)。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("values")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("values")]
|
||||
public List<string>? Values { get; set; }
|
||||
}
|
||||
@ -41,11 +41,7 @@ public class CustomerAccountFlowService : ApplicationService
|
||||
predicate,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
new Dictionary<string, bool>
|
||||
{
|
||||
[nameof(CustomerAccountFlow.CreateTime)] = false,
|
||||
[nameof(CustomerAccountFlow.Id)] = false
|
||||
});
|
||||
BuildAccountFlowSortDictionary(input));
|
||||
|
||||
var total = await FlowRepository.CountAsync(predicate);
|
||||
var customerIds = entities.Select(x => x.CustomerId).Distinct().ToArray();
|
||||
@ -94,27 +90,48 @@ public class CustomerAccountFlowService : ApplicationService
|
||||
&& !string.Equals(bizType, "all", StringComparison.OrdinalIgnoreCase);
|
||||
var hasCustomerFilter = customerId.HasValue;
|
||||
var customerIdValue = customerId.GetValueOrDefault();
|
||||
|
||||
if (input.StartTime.HasValue && input.EndTime.HasValue)
|
||||
{
|
||||
var start = input.StartTime.Value;
|
||||
var end = input.EndTime.Value;
|
||||
return flow =>
|
||||
(!hasCustomerFilter || flow.CustomerId == customerIdValue) &&
|
||||
(!hasBizType || flow.BizType == bizType) &&
|
||||
(!hasKeyword ||
|
||||
flow.SerialNumber.Contains(keyword!) ||
|
||||
(flow.BizNo ?? string.Empty).Contains(keyword!)) &&
|
||||
flow.CreateTime >= start &&
|
||||
flow.CreateTime <= end;
|
||||
}
|
||||
var hasStart = input.StartTime.HasValue;
|
||||
var hasEnd = input.EndTime.HasValue;
|
||||
var start = input.StartTime.GetValueOrDefault();
|
||||
var end = input.EndTime.GetValueOrDefault();
|
||||
var hasAmountMin = input.AmountMin.HasValue;
|
||||
var hasAmountMax = input.AmountMax.HasValue;
|
||||
var amountMin = input.AmountMin.GetValueOrDefault();
|
||||
var amountMax = input.AmountMax.GetValueOrDefault();
|
||||
|
||||
return flow =>
|
||||
(!hasCustomerFilter || flow.CustomerId == customerIdValue) &&
|
||||
(!hasBizType || flow.BizType == bizType) &&
|
||||
(!hasKeyword ||
|
||||
flow.SerialNumber.Contains(keyword!) ||
|
||||
(flow.BizNo ?? string.Empty).Contains(keyword!));
|
||||
(flow.BizNo ?? string.Empty).Contains(keyword!)) &&
|
||||
(!hasStart || flow.CreateTime >= start) &&
|
||||
(!hasEnd || flow.CreateTime <= end) &&
|
||||
(!hasAmountMin || flow.Amount >= amountMin) &&
|
||||
(!hasAmountMax || flow.Amount <= amountMax);
|
||||
}
|
||||
|
||||
private static Dictionary<string, bool> BuildAccountFlowSortDictionary(
|
||||
CustomerAccountFlowPaginatedInput input)
|
||||
{
|
||||
var field = input.SortField?.Trim();
|
||||
var asc = string.Equals(input.SortOrder, "asc", StringComparison.OrdinalIgnoreCase);
|
||||
if (string.Equals(field, "amount", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool>
|
||||
{
|
||||
[nameof(CustomerAccountFlow.Amount)] = asc,
|
||||
[nameof(CustomerAccountFlow.Id)] = false
|
||||
};
|
||||
}
|
||||
|
||||
return new Dictionary<string, bool>
|
||||
{
|
||||
[nameof(CustomerAccountFlow.CreateTime)] = string.Equals(field, "createTime", StringComparison.OrdinalIgnoreCase)
|
||||
? asc
|
||||
: false,
|
||||
[nameof(CustomerAccountFlow.Id)] = false
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetBizTypeName(string bizType)
|
||||
|
||||
@ -66,7 +66,7 @@ public class CustomerService : ApplicationService
|
||||
predicate,
|
||||
pageIndex,
|
||||
pageSize,
|
||||
new Dictionary<string, bool> { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false });
|
||||
BuildCustomerSortDictionary(input));
|
||||
|
||||
var totalCount = await CustomerRepository.CountAsync(predicate);
|
||||
var auditUserNameMap = await UserDisplayNameResolver.ResolveManyAsync(
|
||||
@ -424,6 +424,40 @@ public class CustomerService : ApplicationService
|
||||
(string.IsNullOrWhiteSpace(customerService) || customer.CustomerService == customerService);
|
||||
}
|
||||
|
||||
private static Dictionary<string, bool> BuildCustomerSortDictionary(CustomerPaginatedInput input)
|
||||
{
|
||||
var field = input.SortField?.Trim();
|
||||
var asc = string.Equals(input.SortOrder, "asc", StringComparison.OrdinalIgnoreCase);
|
||||
// Dictionary value: true=asc, false=desc(与仓库约定一致)
|
||||
if (string.Equals(field, "createTime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool> { [nameof(Customer.CreateTime)] = asc, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
if (string.Equals(field, "customerCode", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool> { [nameof(Customer.CustomerCode)] = asc, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
if (string.Equals(field, "customerName", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool> { [nameof(Customer.CustomerName)] = asc, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
if (string.Equals(field, "balance", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(field, "accountBalance", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool> { [nameof(Customer.Balance)] = asc, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
if (string.Equals(field, "updateTime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return new Dictionary<string, bool> { [nameof(Customer.UpdateTime)] = asc, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
return new Dictionary<string, bool> { [nameof(Customer.UpdateTime)] = false, [nameof(Customer.Id)] = false };
|
||||
}
|
||||
|
||||
private async Task<Customer> GetCustomerOrThrowAsync(int id)
|
||||
{
|
||||
var entity = await CustomerRepository.FirstOrDefaultAsync(x => x.Id == id);
|
||||
|
||||
@ -282,6 +282,9 @@ public partial class OrderService
|
||||
CreateTimeStart = start,
|
||||
CreateTimeEnd = filter.CreateTimeEnd,
|
||||
CustomerId = filter.CustomerId,
|
||||
FieldFilters = filter.FieldFilters,
|
||||
SortField = filter.SortField,
|
||||
SortOrder = filter.SortOrder,
|
||||
ScopeOrganizationIds = filter.ScopeOrganizationIds,
|
||||
ScopeSecretLevels = filter.ScopeSecretLevels,
|
||||
ScopeDataScope = filter.ScopeDataScope,
|
||||
|
||||
@ -68,9 +68,14 @@ public partial class OrderService : ApplicationService
|
||||
/// 按状态聚合统计订单数量(单次查询,供 Tab 角标展示)。
|
||||
/// </summary>
|
||||
[HttpGet("/order/status-counts")]
|
||||
public async Task<OrderStatusCountsOutput> GetStatusCountsAsync()
|
||||
public async Task<OrderStatusCountsOutput> GetStatusCountsAsync(
|
||||
[FromQuery] DateTime? createTimeStart = null,
|
||||
[FromQuery] DateTime? createTimeEnd = null)
|
||||
{
|
||||
var filter = await BuildScopedListFilterAsync();
|
||||
filter.CreateTimeStart = createTimeStart;
|
||||
filter.CreateTimeEnd = createTimeEnd;
|
||||
EnsureOrderListTimeWindow(filter);
|
||||
var rows = await OrderRepository.GetStatusCountsByFilterAsync(filter);
|
||||
var counts = rows.ToDictionary(
|
||||
x => x.Status,
|
||||
@ -316,7 +321,14 @@ public partial class OrderService : ApplicationService
|
||||
null);
|
||||
var filter = BuildListFilter(input, customerId);
|
||||
await ApplyInternalOrderDataScopeAsync(filter);
|
||||
var orders = await OrderRepository.GetFilteredListAsync(filter);
|
||||
|
||||
const int maxExportRows = 10000;
|
||||
var (orders, total) = await OrderRepository.GetFilteredPagedListAsync(1, maxExportRows, filter);
|
||||
if (total > maxExportRows)
|
||||
{
|
||||
throw new BusinessException($"导出结果超过 {maxExportRows} 条(当前 {total}),请缩小筛选条件后再导出");
|
||||
}
|
||||
|
||||
var fileName = $"orders-{DateTime.Now:yyyyMMddHHmmss}.xlsx";
|
||||
var directory = GetOrderExportDirectory();
|
||||
Directory.CreateDirectory(directory);
|
||||
@ -507,28 +519,266 @@ public partial class OrderService : ApplicationService
|
||||
};
|
||||
}
|
||||
|
||||
private static OrderListFilter BuildListFilter(OrderPaginatedInput input, int? customerId) =>
|
||||
new()
|
||||
private static OrderListFilter BuildListFilter(OrderPaginatedInput input, int? customerId)
|
||||
{
|
||||
var filter = new OrderListFilter
|
||||
{
|
||||
Status = input.Status,
|
||||
HandleStatus = input.HandleStatus,
|
||||
OrderSource = input.OrderSource,
|
||||
Keyword = input.Keyword,
|
||||
Keyword = NormalizeOrderKeyword(input.Keyword),
|
||||
CreateTimeStart = input.CreateTimeStart,
|
||||
CreateTimeEnd = input.CreateTimeEnd,
|
||||
CustomerId = customerId
|
||||
CustomerId = customerId,
|
||||
FieldFilters = NormalizeOrderFieldFilters(input.Filters),
|
||||
SortField = NormalizeOrderSortField(input.SortField),
|
||||
SortOrder = NormalizeOrderSortOrder(input.SortOrder)
|
||||
};
|
||||
EnsureOrderListTimeWindow(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
private static OrderListFilter BuildListFilter(OrderExportInput input, int? customerId) =>
|
||||
new()
|
||||
private static OrderListFilter BuildListFilter(OrderExportInput input, int? customerId)
|
||||
{
|
||||
var filter = new OrderListFilter
|
||||
{
|
||||
Status = input.Status,
|
||||
OrderSource = input.OrderSource,
|
||||
Keyword = input.Keyword,
|
||||
Keyword = NormalizeOrderKeyword(input.Keyword),
|
||||
CreateTimeStart = input.CreateTimeStart,
|
||||
CreateTimeEnd = input.CreateTimeEnd,
|
||||
CustomerId = customerId
|
||||
CustomerId = customerId,
|
||||
FieldFilters = NormalizeOrderFieldFilters(input.Filters),
|
||||
SortField = NormalizeOrderSortField(input.SortField),
|
||||
SortOrder = NormalizeOrderSortOrder(input.SortOrder)
|
||||
};
|
||||
EnsureOrderListTimeWindow(filter);
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>订单列表默认时间窗(天)。无精确单号且未传时间时兜底,防全表扫。</summary>
|
||||
private const int OrderListDefaultDays = 90;
|
||||
|
||||
/// <summary>
|
||||
/// 无创建时间且非「精确单号查询」时,自动限制近 N 天。
|
||||
/// </summary>
|
||||
private static void EnsureOrderListTimeWindow(OrderListFilter filter)
|
||||
{
|
||||
if (filter.CreateTimeStart.HasValue || filter.CreateTimeEnd.HasValue)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasNarrowOrderLookup(filter))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
filter.CreateTimeStart = DateTime.Now.Date.AddDays(-(OrderListDefaultDays - 1));
|
||||
}
|
||||
|
||||
private static bool HasNarrowOrderLookup(OrderListFilter filter)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(filter.Keyword))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filter.FieldFilters == null || filter.FieldFilters.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var numberFields = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"serviceNo", "clientNo", "trackNo", "downstreamServiceNo"
|
||||
};
|
||||
|
||||
return filter.FieldFilters.Any(f =>
|
||||
numberFields.Contains(f.Field)
|
||||
&& (f.Op is "eq" or "in" or "like")
|
||||
&& (!string.IsNullOrWhiteSpace(f.Value) || (f.Values?.Count ?? 0) > 0));
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> OrderFieldFilterWhitelist = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"serviceNo",
|
||||
"clientNo",
|
||||
"trackNo",
|
||||
"downstreamServiceNo",
|
||||
"createTime",
|
||||
"orderStatus",
|
||||
"productCode",
|
||||
"channelCode",
|
||||
"orderSource",
|
||||
"payableAmount",
|
||||
"isPaid",
|
||||
"predeclareFailReason",
|
||||
"allocationPendingReason",
|
||||
"payTime",
|
||||
"shipTime",
|
||||
"receiveTime",
|
||||
"signTime"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> OrderFieldFilterOps = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"like", "eq", "ne", "in", "notIn", "gt", "gte", "lt", "lte", "between",
|
||||
"empty", "isNull", "isNotNull"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> OrderSortFieldWhitelist = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"createTime",
|
||||
"updateTime",
|
||||
"serviceNo",
|
||||
"clientNo",
|
||||
"trackNo",
|
||||
"payableAmount",
|
||||
"payTime",
|
||||
"shipTime",
|
||||
"receiveTime",
|
||||
"signTime"
|
||||
};
|
||||
|
||||
/// <summary>单次列表最多接受的 filters 条数。</summary>
|
||||
private const int MaxOrderFieldFilters = 20;
|
||||
|
||||
/// <summary>IN / NOT IN 单字段最多值数量(防几万单号打爆 SQL/参数包)。</summary>
|
||||
private const int MaxOrderInValues = 500;
|
||||
|
||||
/// <summary>单值最大长度(单号/文本)。</summary>
|
||||
private const int MaxOrderFilterValueLength = 128;
|
||||
|
||||
/// <summary>keyword 最大长度。</summary>
|
||||
private const int MaxOrderKeywordLength = 200;
|
||||
|
||||
private static string? NormalizeOrderKeyword(string? keyword)
|
||||
{
|
||||
var text = keyword?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return text.Length <= MaxOrderKeywordLength
|
||||
? text
|
||||
: text[..MaxOrderKeywordLength];
|
||||
}
|
||||
|
||||
private static string? TruncateFilterValue(string? value)
|
||||
{
|
||||
var text = value?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return text.Length <= MaxOrderFilterValueLength
|
||||
? text
|
||||
: text[..MaxOrderFilterValueLength];
|
||||
}
|
||||
|
||||
private static string? NormalizeOrderSortField(string? sortField)
|
||||
{
|
||||
var field = sortField?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(field) || !OrderSortFieldWhitelist.Contains(field))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
private static string? NormalizeOrderSortOrder(string? sortOrder)
|
||||
{
|
||||
var order = sortOrder?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(order))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (string.Equals(order, "asc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "asc";
|
||||
}
|
||||
|
||||
if (string.Equals(order, "desc", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "desc";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static List<OrderListFieldFilter>? NormalizeOrderFieldFilters(
|
||||
List<OrderListFieldFilterDto>? filters)
|
||||
{
|
||||
if (filters == null || filters.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new List<OrderListFieldFilter>();
|
||||
foreach (var f in filters.Take(MaxOrderFieldFilters))
|
||||
{
|
||||
var field = f.Field?.Trim() ?? string.Empty;
|
||||
var op = string.IsNullOrWhiteSpace(f.Op) ? "like" : f.Op.Trim();
|
||||
if (!OrderFieldFilterWhitelist.Contains(field) || !OrderFieldFilterOps.Contains(op))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
op = op.ToLowerInvariant();
|
||||
if (string.Equals(op, "isNull", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
op = "empty";
|
||||
}
|
||||
|
||||
var values = (f.Values ?? [])
|
||||
.Select(x => TruncateFilterValue(x))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Cast<string>()
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Take(MaxOrderInValues)
|
||||
.ToList();
|
||||
if (values.Count == 0 && !string.IsNullOrWhiteSpace(f.Value)
|
||||
&& (op is "in" or "notin"))
|
||||
{
|
||||
values = f.Value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(x => TruncateFilterValue(x))
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Cast<string>()
|
||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||
.Take(MaxOrderInValues)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
var needsValue = op is not ("empty" or "isnotnull");
|
||||
var truncatedValue = TruncateFilterValue(f.Value);
|
||||
var truncatedValue2 = TruncateFilterValue(f.Value2);
|
||||
if (needsValue && values.Count == 0 && string.IsNullOrWhiteSpace(truncatedValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (op is "between" && string.IsNullOrWhiteSpace(truncatedValue2) && values.Count < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Add(new OrderListFieldFilter
|
||||
{
|
||||
Field = field,
|
||||
Op = op,
|
||||
Value = truncatedValue,
|
||||
Value2 = truncatedValue2,
|
||||
Values = values.Count > 0 ? values : null
|
||||
});
|
||||
}
|
||||
|
||||
return result.Count == 0 ? null : result;
|
||||
}
|
||||
|
||||
private async Task EnsureCustomerCanAccessOrderAsync(Order order)
|
||||
{
|
||||
|
||||
17
src/Oms.Domain/OrdersAggregate/OrderListFieldFilter.cs
Normal file
17
src/Oms.Domain/OrdersAggregate/OrderListFieldFilter.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace Oms.Domain.OrdersAggregate;
|
||||
|
||||
/// <summary>
|
||||
/// 订单列表字段级筛选(仓储侧已规范化)。
|
||||
/// </summary>
|
||||
public class OrderListFieldFilter
|
||||
{
|
||||
public string Field { get; set; } = string.Empty;
|
||||
|
||||
public string Op { get; set; } = "like";
|
||||
|
||||
public string? Value { get; set; }
|
||||
|
||||
public string? Value2 { get; set; }
|
||||
|
||||
public List<string>? Values { get; set; }
|
||||
}
|
||||
@ -19,6 +19,21 @@ public class OrderListFilter
|
||||
|
||||
public int? CustomerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表头字段级筛选(已规范化的白名单条件)。
|
||||
/// </summary>
|
||||
public List<OrderListFieldFilter>? FieldFilters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已规范化的排序字段(白名单);空则默认 UpdateTime Desc。
|
||||
/// </summary>
|
||||
public string? SortField { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// asc / desc。
|
||||
/// </summary>
|
||||
public string? SortOrder { get; set; }
|
||||
|
||||
public List<string>? ScopeOrganizationIds { get; set; }
|
||||
|
||||
public List<int>? ScopeSecretLevels { get; set; }
|
||||
|
||||
@ -19,9 +19,8 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
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
|
||||
.OrderBy(x => x.UpdateTime, OrderByType.Desc)
|
||||
.OrderBy(x => x.Id, OrderByType.Desc)
|
||||
.ToPageListAsync(pageIndex, pageSize, cancellationToken);
|
||||
return (items, total);
|
||||
}
|
||||
@ -31,9 +30,7 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var db = await GetDbContextAsync(cancellationToken);
|
||||
return await ApplyListFilter(db.Queryable<Order>(), filter)
|
||||
.OrderBy(x => x.UpdateTime, OrderByType.Desc)
|
||||
.OrderBy(x => x.Id, OrderByType.Desc)
|
||||
return await ApplyListSort(ApplyListFilter(db.Queryable<Order>(), filter), filter)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
@ -98,11 +95,502 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
query = query.Where(x => x.OrderSource == orderSource);
|
||||
}
|
||||
|
||||
query = ApplyFieldFilters(query, filter.FieldFilters);
|
||||
|
||||
query = OrderListScopeFilterApplier.Apply(query, filter);
|
||||
|
||||
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
|
||||
{
|
||||
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, "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);
|
||||
@ -523,6 +1011,9 @@ public class OrderRepository : OmsSqlSugarRepository<Order>, IOrderRepository, I
|
||||
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user