feat(order): 增加 IsPaid 字段并修复列表状态筛选
- t_order 新增 IsPaid,支付/出库以该字段判断;含迁移脚本 - 列表接口补充 productName、channelName - 修复 BuildOrderPredicate 状态条件 SqlSugar 翻译丢失 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
e7fd69b644
commit
095ad66717
1
db.sql
1
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币种',
|
||||
|
||||
7
sql/migrate_t_order_is_paid.sql
Normal file
7
sql/migrate_t_order_is_paid.sql
Normal file
@ -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;
|
||||
@ -30,11 +30,21 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public string? ProductCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 销售产品名称。
|
||||
/// </summary>
|
||||
public string? ProductName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 渠道编码。
|
||||
/// </summary>
|
||||
public string? ChannelCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务渠道名称。
|
||||
/// </summary>
|
||||
public string? ChannelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 货物属性。
|
||||
/// </summary>
|
||||
@ -95,6 +105,11 @@ public class OrderOutput
|
||||
/// </summary>
|
||||
public DateTime? PayTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已支付。
|
||||
/// </summary>
|
||||
public bool IsPaid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签收时间。
|
||||
/// </summary>
|
||||
|
||||
@ -21,6 +21,7 @@ public class OrderPaginatedInput
|
||||
/// 状态:all 或 OrderStatusCodes 枚举值。
|
||||
/// </summary>
|
||||
[Newtonsoft.Json.JsonProperty("status")]
|
||||
[global::System.Text.Json.Serialization.JsonPropertyName("status")]
|
||||
public string? Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@ -80,6 +80,11 @@ public class OrderPaginatedOutput
|
||||
/// </summary>
|
||||
public DateTime? PayTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已支付:0-否,1-是。
|
||||
/// </summary>
|
||||
public byte IsPaid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签收时间。
|
||||
/// </summary>
|
||||
|
||||
@ -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<OutboundRecordOutput> { 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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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("订单未付款,禁止出库");
|
||||
}
|
||||
|
||||
@ -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. 避免把 <see cref="Nullable{T}.HasValue"/> / <see cref="Nullable{T}.Value"/> 放进可翻译 SQL 的表达式中,
|
||||
/// 从而规避 MySQL 下时间条件被错误翻译的问题。
|
||||
/// 从而规避 MySQL 下时间条件被错误翻译的问题;
|
||||
/// 4. 状态筛选同样显式分支,避免 <c>statusValue == null || order.OrderStatus == statusValue</c> 被 SqlSugar 误译。
|
||||
/// </remarks>
|
||||
private static Expression<Func<Order, bool>> 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<DateTime> 判空时生成异常 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,12 @@ public partial class OrderService : ApplicationService
|
||||
if (createTimeEnd.HasValue)
|
||||
{
|
||||
var end = createTimeEnd.Value;
|
||||
if (hasStatusFilter)
|
||||
{
|
||||
var statusFilter = orderStatusFilter!;
|
||||
return order =>
|
||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
||||
(statusValue == null || order.OrderStatus == statusValue) &&
|
||||
order.OrderStatus == statusFilter &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
||||
@ -585,7 +616,27 @@ public partial class OrderService : ApplicationService
|
||||
|
||||
return order =>
|
||||
(!hasCustomerFilter || order.CustomerId == customerIdValue) &&
|
||||
(statusValue == null || order.OrderStatus == statusValue) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
||||
(order.TrackNo ?? string.Empty).Contains(keyword!)) &&
|
||||
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) &&
|
||||
(!hasKeywordFilter ||
|
||||
order.ServiceNo.Contains(keyword!) ||
|
||||
(order.ClientNo ?? string.Empty).Contains(keyword!) ||
|
||||
|
||||
@ -48,6 +48,11 @@ public class Order : OmsAuditedAggregateRoot
|
||||
|
||||
public DateTime? PayTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否已支付:0-否,1-是。
|
||||
/// </summary>
|
||||
public byte IsPaid { get; set; }
|
||||
|
||||
public DateTime? SignTime { get; set; }
|
||||
|
||||
public byte IsCod { get; set; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user