diff --git a/db.sql b/db.sql
index e02fc29..1bcf30f 100644
--- a/db.sql
+++ b/db.sql
@@ -73,6 +73,7 @@ CREATE TABLE `t_order` (
`ReceiveTime` DATETIME NULL COMMENT '收货时间',
`ShipTime` DATETIME NULL COMMENT '发货时间',
`PayTime` DATETIME NULL COMMENT '支付时间',
+ `IsPaid` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已支付:0-否,1-是',
`SignTime` DATETIME NULL COMMENT '签收时间',
`IsCod` TINYINT NOT NULL DEFAULT 0 COMMENT '是否COD:0-否,1-是',
`CodCurrency` VARCHAR(16) NULL COMMENT 'COD币种',
diff --git a/sql/migrate_t_order_is_paid.sql b/sql/migrate_t_order_is_paid.sql
new file mode 100644
index 0000000..058f418
--- /dev/null
+++ b/sql/migrate_t_order_is_paid.sql
@@ -0,0 +1,7 @@
+-- 订单表增加是否已支付字段,并按历史 PayTime 回填
+ALTER TABLE `t_order`
+ ADD COLUMN `IsPaid` TINYINT NOT NULL DEFAULT 0 COMMENT '是否已支付:0-否,1-是' AFTER `PayTime`;
+
+UPDATE `t_order`
+SET `IsPaid` = 1
+WHERE `PayTime` IS NOT NULL;
diff --git a/src/Oms.Application/Dtos/Orders/OrderOutput.cs b/src/Oms.Application/Dtos/Orders/OrderOutput.cs
index b8d347a..f5de7ef 100644
--- a/src/Oms.Application/Dtos/Orders/OrderOutput.cs
+++ b/src/Oms.Application/Dtos/Orders/OrderOutput.cs
@@ -30,11 +30,21 @@ public class OrderOutput
///
public string? ProductCode { get; set; }
+ ///
+ /// 销售产品名称。
+ ///
+ public string? ProductName { get; set; }
+
///
/// 渠道编码。
///
public string? ChannelCode { get; set; }
+ ///
+ /// 服务渠道名称。
+ ///
+ public string? ChannelName { get; set; }
+
///
/// 货物属性。
///
@@ -95,6 +105,11 @@ public class OrderOutput
///
public DateTime? PayTime { get; set; }
+ ///
+ /// 是否已支付。
+ ///
+ public bool IsPaid { get; set; }
+
///
/// 签收时间。
///
diff --git a/src/Oms.Application/Dtos/Orders/OrderPaginatedInput.cs b/src/Oms.Application/Dtos/Orders/OrderPaginatedInput.cs
index 642ffb8..159b148 100644
--- a/src/Oms.Application/Dtos/Orders/OrderPaginatedInput.cs
+++ b/src/Oms.Application/Dtos/Orders/OrderPaginatedInput.cs
@@ -21,6 +21,7 @@ public class OrderPaginatedInput
/// 状态:all 或 OrderStatusCodes 枚举值。
///
[Newtonsoft.Json.JsonProperty("status")]
+ [global::System.Text.Json.Serialization.JsonPropertyName("status")]
public string? Status { get; set; }
///
diff --git a/src/Oms.Application/Dtos/Orders/OrderPaginatedOutput.cs b/src/Oms.Application/Dtos/Orders/OrderPaginatedOutput.cs
index 0a14ee6..a3dd806 100644
--- a/src/Oms.Application/Dtos/Orders/OrderPaginatedOutput.cs
+++ b/src/Oms.Application/Dtos/Orders/OrderPaginatedOutput.cs
@@ -80,6 +80,11 @@ public class OrderPaginatedOutput
///
public DateTime? PayTime { get; set; }
+ ///
+ /// 是否已支付:0-否,1-是。
+ ///
+ public byte IsPaid { get; set; }
+
///
/// 签收时间。
///
diff --git a/src/Oms.Application/Services/Operations/OutboundRecordService.cs b/src/Oms.Application/Services/Operations/OutboundRecordService.cs
index 64bcc89..0ebd563 100644
--- a/src/Oms.Application/Services/Operations/OutboundRecordService.cs
+++ b/src/Oms.Application/Services/Operations/OutboundRecordService.cs
@@ -8,6 +8,7 @@ using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OperationsAggregate.Entities;
using Oms.Domain.OperationsAggregate.Repositories;
using Oms.Domain.OrdersAggregate.Consts;
+using Oms.Domain.OrdersAggregate.Entities;
using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.SystemAggregate.Repositories;
@@ -45,7 +46,7 @@ public class OutboundRecordService : ApplicationService
throw new BusinessException("未找到对应订单,请检查单号");
}
- EnsureOutboundAllowed(order.OrderStatus);
+ EnsureOutboundAllowed(order);
var output = new OutboundOrderLookupOutput
{
@@ -114,7 +115,7 @@ public class OutboundRecordService : ApplicationService
throw new BusinessException("未找到对应订单,请检查单号");
}
- EnsureOutboundAllowed(order.OrderStatus);
+ EnsureOutboundAllowed(order);
int? salesProductId = input.SalesProductId;
if (!salesProductId.HasValue && !string.IsNullOrWhiteSpace(order.ProductCode))
@@ -226,13 +227,14 @@ public class OutboundRecordService : ApplicationService
return new OmsItemsResult { Items = items };
}
- private static void EnsureOutboundAllowed(string orderStatus)
+ private static void EnsureOutboundAllowed(Order order)
{
- if (orderStatus is OrderStatusCodes.PendingPayment or OrderStatusCodes.Received)
+ if (order.IsPaid == 0)
{
throw new BusinessException("订单未付款,禁止出库");
}
+ var orderStatus = order.OrderStatus;
if (orderStatus != OrderStatusCodes.PendingShip)
{
if (orderStatus is OrderStatusCodes.PendingOnline or OrderStatusCodes.Shipping
diff --git a/src/Oms.Application/Services/Orders/OrderService.Helpers.cs b/src/Oms.Application/Services/Orders/OrderService.Helpers.cs
index 79dd976..3b6af15 100644
--- a/src/Oms.Application/Services/Orders/OrderService.Helpers.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.Helpers.cs
@@ -39,6 +39,25 @@ public partial class OrderService
var feeMap = fees.GroupBy(x => x.OrderId).ToDictionary(x => x.Key, x => x.ToList());
var warehouseMap = warehouses.ToDictionary(x => x.Id);
+ var productCodes = orders
+ .Where(x => !string.IsNullOrWhiteSpace(x.ProductCode))
+ .Select(x => x.ProductCode!.Trim())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+ var channelCodes = orders
+ .Where(x => !string.IsNullOrWhiteSpace(x.ChannelCode))
+ .Select(x => x.ChannelCode!.Trim())
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+ var products = productCodes.Length == 0
+ ? []
+ : await SalesProductRepository.GetListAsync(x => productCodes.Contains(x.ProductCode));
+ var channels = channelCodes.Length == 0
+ ? []
+ : await ServiceChannelRepository.GetListAsync(x => channelCodes.Contains(x.ChannelCode));
+ var productMap = products.ToDictionary(x => x.ProductCode, StringComparer.OrdinalIgnoreCase);
+ var channelMap = channels.ToDictionary(x => x.ChannelCode, StringComparer.OrdinalIgnoreCase);
+
return orders.Select(order =>
{
senderMap.TryGetValue(order.Id, out var sender);
@@ -58,7 +77,15 @@ public partial class OrderService
TrackNo = order.TrackNo,
ServiceNo = order.ServiceNo,
ProductCode = order.ProductCode,
+ ProductName = !string.IsNullOrWhiteSpace(order.ProductCode)
+ && productMap.TryGetValue(order.ProductCode.Trim(), out var product)
+ ? product.ProductName
+ : null,
ChannelCode = order.ChannelCode,
+ ChannelName = !string.IsNullOrWhiteSpace(order.ChannelCode)
+ && channelMap.TryGetValue(order.ChannelCode.Trim(), out var channel)
+ ? channel.ChannelName
+ : null,
CargoAttribute = order.CargoAttribute,
WarehouseId = order.WarehouseId,
Warehouse = order.WarehouseId.HasValue && warehouseMap.TryGetValue(order.WarehouseId.Value, out var warehouse)
@@ -73,6 +100,7 @@ public partial class OrderService
ReceiveTime = order.ReceiveTime,
ShipTime = order.ShipTime,
PayTime = order.PayTime,
+ IsPaid = order.IsPaid > 0,
SignTime = order.SignTime,
IsIntercept = order.IsIntercept > 0,
IsRemote = order.IsRemote > 0,
@@ -101,7 +129,9 @@ public partial class OrderService
x.TrackNo,
x.ServiceNo,
x.ProductCode,
+ x.ProductName,
x.ChannelCode,
+ x.ChannelName,
x.CargoAttribute,
x.WarehouseId,
x.Warehouse,
@@ -111,6 +141,7 @@ public partial class OrderService
x.ReceiveTime,
x.ShipTime,
x.PayTime,
+ x.IsPaid,
x.SignTime,
x.IsCod,
x.CodCurrency,
diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
index a4f895f..d7285c5 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
@@ -1316,7 +1316,7 @@ public partial class OrderService
throw new BusinessException("订单未绑定销售产品,无法出库计费");
}
- if (order.OrderStatus is OrderStatusCodes.PendingPayment or OrderStatusCodes.Received)
+ if (order.IsPaid == 0)
{
throw new BusinessException("订单未付款,禁止出库");
}
diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs
index 7f21b87..b2819f4 100644
--- a/src/Oms.Application/Services/Orders/OrderService.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.cs
@@ -114,7 +114,7 @@ public partial class OrderService : ApplicationService
public async Task PayAsync(OrderActionInput input)
{
var order = await GetOrderOrThrowAsync(input.Id);
- if (order.OrderStatus == OrderStatusCodes.PendingShip && order.PayTime.HasValue)
+ if (order.IsPaid > 0)
{
throw new BusinessException("订单已支付");
}
@@ -153,6 +153,7 @@ public partial class OrderService : ApplicationService
$"订单支付 {order.ServiceNo}");
order.PayTime = DateTime.Now;
+ order.IsPaid = 1;
order.OrderStatus = OrderStatusCodes.PendingShip;
await UpdateOrderAsync(order);
}
@@ -523,7 +524,8 @@ public partial class OrderService : ApplicationService
/// 1. 先把状态和关键词做统一清洗;
/// 2. 再按时间区间是否存在拆分成不同表达式;
/// 3. 避免把 / 放进可翻译 SQL 的表达式中,
- /// 从而规避 MySQL 下时间条件被错误翻译的问题。
+ /// 从而规避 MySQL 下时间条件被错误翻译的问题;
+ /// 4. 状态筛选同样显式分支,避免 statusValue == null || order.OrderStatus == statusValue 被 SqlSugar 误译。
///
private static Expression> BuildOrderPredicate(
string? status,
@@ -534,21 +536,35 @@ public partial class OrderService : ApplicationService
{
status = status?.Trim();
keyword = keyword?.Trim();
- var statusValue = !string.IsNullOrWhiteSpace(status) && !string.Equals(status, "all", StringComparison.OrdinalIgnoreCase)
+ var orderStatusFilter = !string.IsNullOrWhiteSpace(status) && !string.Equals(status, "all", StringComparison.OrdinalIgnoreCase)
? status
: null;
+ var hasStatusFilter = orderStatusFilter != null;
var hasKeywordFilter = !string.IsNullOrWhiteSpace(keyword);
var hasCustomerFilter = customerId.HasValue;
var customerIdValue = customerId.GetValueOrDefault();
- // 显式拆开时间分支,避免 SqlSugar 在翻译 Nullable 判空时生成异常 SQL。
+ // 状态与时间均显式分支,避免 SqlSugar 翻译「闭包变量 == null || 字段 == 变量」时丢失条件。
if (createTimeStart.HasValue && createTimeEnd.HasValue)
{
var start = createTimeStart.Value;
var end = createTimeEnd.Value;
+ if (hasStatusFilter)
+ {
+ var statusFilter = orderStatusFilter!;
+ return order =>
+ (!hasCustomerFilter || order.CustomerId == customerIdValue) &&
+ order.OrderStatus == statusFilter &&
+ (!hasKeywordFilter ||
+ order.ServiceNo.Contains(keyword!) ||
+ (order.ClientNo ?? string.Empty).Contains(keyword!) ||
+ (order.TrackNo ?? string.Empty).Contains(keyword!)) &&
+ order.CreateTime >= start &&
+ order.CreateTime <= end;
+ }
+
return order =>
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
- (statusValue == null || order.OrderStatus == statusValue) &&
(!hasKeywordFilter ||
order.ServiceNo.Contains(keyword!) ||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
@@ -560,9 +576,21 @@ public partial class OrderService : ApplicationService
if (createTimeStart.HasValue)
{
var start = createTimeStart.Value;
+ if (hasStatusFilter)
+ {
+ var statusFilter = orderStatusFilter!;
+ return order =>
+ (!hasCustomerFilter || order.CustomerId == customerIdValue) &&
+ order.OrderStatus == statusFilter &&
+ (!hasKeywordFilter ||
+ order.ServiceNo.Contains(keyword!) ||
+ (order.ClientNo ?? string.Empty).Contains(keyword!) ||
+ (order.TrackNo ?? string.Empty).Contains(keyword!)) &&
+ order.CreateTime >= start;
+ }
+
return order =>
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
- (statusValue == null || order.OrderStatus == statusValue) &&
(!hasKeywordFilter ||
order.ServiceNo.Contains(keyword!) ||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
@@ -573,9 +601,21 @@ public partial class OrderService : ApplicationService
if (createTimeEnd.HasValue)
{
var end = createTimeEnd.Value;
+ if (hasStatusFilter)
+ {
+ var statusFilter = orderStatusFilter!;
+ return order =>
+ (!hasCustomerFilter || order.CustomerId == customerIdValue) &&
+ order.OrderStatus == statusFilter &&
+ (!hasKeywordFilter ||
+ order.ServiceNo.Contains(keyword!) ||
+ (order.ClientNo ?? string.Empty).Contains(keyword!) ||
+ (order.TrackNo ?? string.Empty).Contains(keyword!)) &&
+ order.CreateTime <= end;
+ }
+
return order =>
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
- (statusValue == null || order.OrderStatus == statusValue) &&
(!hasKeywordFilter ||
order.ServiceNo.Contains(keyword!) ||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
@@ -583,9 +623,20 @@ public partial class OrderService : ApplicationService
order.CreateTime <= end;
}
+ if (hasStatusFilter)
+ {
+ var statusFilter = orderStatusFilter!;
+ return order =>
+ (!hasCustomerFilter || order.CustomerId == customerIdValue) &&
+ order.OrderStatus == statusFilter &&
+ (!hasKeywordFilter ||
+ order.ServiceNo.Contains(keyword!) ||
+ (order.ClientNo ?? string.Empty).Contains(keyword!) ||
+ (order.TrackNo ?? string.Empty).Contains(keyword!));
+ }
+
return order =>
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
- (statusValue == null || order.OrderStatus == statusValue) &&
(!hasKeywordFilter ||
order.ServiceNo.Contains(keyword!) ||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
diff --git a/src/Oms.Domain/OrdersAggregate/Entities/Order.cs b/src/Oms.Domain/OrdersAggregate/Entities/Order.cs
index fa0c6cf..c35bb49 100644
--- a/src/Oms.Domain/OrdersAggregate/Entities/Order.cs
+++ b/src/Oms.Domain/OrdersAggregate/Entities/Order.cs
@@ -48,6 +48,11 @@ public class Order : OmsAuditedAggregateRoot
public DateTime? PayTime { get; set; }
+ ///
+ /// 是否已支付:0-否,1-是。
+ ///
+ public byte IsPaid { get; set; }
+
public DateTime? SignTime { get; set; }
public byte IsCod { get; set; }