From 095ad66717d240e1ab8d6a02fb5a70b1579ededb Mon Sep 17 00:00:00 2001 From: zzh <1368735060@qq.com> Date: Sat, 27 Jun 2026 19:13:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(order):=20=E5=A2=9E=E5=8A=A0=20IsPaid=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=B9=B6=E4=BF=AE=E5=A4=8D=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - t_order 新增 IsPaid,支付/出库以该字段判断;含迁移脚本 - 列表接口补充 productName、channelName - 修复 BuildOrderPredicate 状态条件 SqlSugar 翻译丢失 Co-authored-by: Cursor --- db.sql | 1 + sql/migrate_t_order_is_paid.sql | 7 ++ .../Dtos/Orders/OrderOutput.cs | 15 +++++ .../Dtos/Orders/OrderPaginatedInput.cs | 1 + .../Dtos/Orders/OrderPaginatedOutput.cs | 5 ++ .../Operations/OutboundRecordService.cs | 10 +-- .../Services/Orders/OrderService.Helpers.cs | 31 +++++++++ .../Orders/OrderService.ParcelBilling.cs | 2 +- .../Services/Orders/OrderService.cs | 67 ++++++++++++++++--- .../OrdersAggregate/Entities/Order.cs | 5 ++ 10 files changed, 131 insertions(+), 13 deletions(-) create mode 100644 sql/migrate_t_order_is_paid.sql 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; }