From fdbdfa81248bf604978ef185b43c1910eb33b60c Mon Sep 17 00:00:00 2001
From: zzh <1368735060@qq.com>
Date: Sat, 27 Jun 2026 11:56:06 +0800
Subject: [PATCH] 20260627
---
sql/migrate_t_customer_account_flow.sql | 1 +
...te_t_customer_account_flow_concurrency.sql | 8 ++
.../Operations/OutboundOrderLookupOutput.cs | 77 ++++++++++++
.../Operations/OutboundRecordService.cs | 113 +++++++++++++++++-
.../Orders/OrderService.ParcelBilling.cs | 2 +-
.../Services/Orders/OrderService.cs | 2 +-
6 files changed, 199 insertions(+), 4 deletions(-)
create mode 100644 sql/migrate_t_customer_account_flow_concurrency.sql
create mode 100644 src/Oms.Application/Dtos/Operations/OutboundOrderLookupOutput.cs
diff --git a/sql/migrate_t_customer_account_flow.sql b/sql/migrate_t_customer_account_flow.sql
index 9e9078b..25f4074 100644
--- a/sql/migrate_t_customer_account_flow.sql
+++ b/sql/migrate_t_customer_account_flow.sql
@@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS `t_customer_account_flow` (
`Currency` varchar(8) NOT NULL DEFAULT 'CNY' COMMENT '币种',
`Remark` varchar(512) DEFAULT NULL COMMENT '备注',
`IsDeleted` tinyint(1) NOT NULL DEFAULT '0',
+ `ConcurrencyStamp` char(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` int NOT NULL DEFAULT '0',
`CreateTime` datetime NOT NULL,
`UpdateUserId` int DEFAULT NULL,
diff --git a/sql/migrate_t_customer_account_flow_concurrency.sql b/sql/migrate_t_customer_account_flow_concurrency.sql
new file mode 100644
index 0000000..74e908f
--- /dev/null
+++ b/sql/migrate_t_customer_account_flow_concurrency.sql
@@ -0,0 +1,8 @@
+-- 已执行旧版 migrate_t_customer_account_flow.sql 的库补 ConcurrencyStamp(与 AggregateRoot 实体对齐)
+-- 勿在 ALTER 中使用 DEFAULT(UUID()),复制环境会报 Error 1674
+ALTER TABLE `t_customer_account_flow`
+ ADD COLUMN `ConcurrencyStamp` char(36) NOT NULL DEFAULT '' COMMENT '并发戳' AFTER `IsDeleted`;
+
+UPDATE `t_customer_account_flow`
+ SET `ConcurrencyStamp` = UUID()
+ WHERE `ConcurrencyStamp` = '';
diff --git a/src/Oms.Application/Dtos/Operations/OutboundOrderLookupOutput.cs b/src/Oms.Application/Dtos/Operations/OutboundOrderLookupOutput.cs
new file mode 100644
index 0000000..95259da
--- /dev/null
+++ b/src/Oms.Application/Dtos/Operations/OutboundOrderLookupOutput.cs
@@ -0,0 +1,77 @@
+namespace Oms.Application.Dtos.Operations;
+
+///
+/// 出库扫描按单号查订单(只读展示销售产品、目的国、服务渠道)。
+///
+public class OutboundOrderLookupOutput
+{
+ ///
+ /// 订单主键。
+ ///
+ public int OrderId { get; set; }
+
+ ///
+ /// 服务单号。
+ ///
+ public string ServiceNo { get; set; } = string.Empty;
+
+ ///
+ /// 客户单号。
+ ///
+ public string? ClientNo { get; set; }
+
+ ///
+ /// 跟踪号。
+ ///
+ public string? TrackNo { get; set; }
+
+ ///
+ /// 产品编码。
+ ///
+ public string? ProductCode { get; set; }
+
+ ///
+ /// 销售产品主键。
+ ///
+ public int? SalesProductId { get; set; }
+
+ ///
+ /// 销售产品名称。
+ ///
+ public string? SalesProductName { get; set; }
+
+ ///
+ /// 目的国家编码。
+ ///
+ public string? DestinationCountry { get; set; }
+
+ ///
+ /// 目的国家名称。
+ ///
+ public string? DestinationCountryName { get; set; }
+
+ ///
+ /// 服务渠道主键。
+ ///
+ public int? ServiceChannelId { get; set; }
+
+ ///
+ /// 服务渠道编码。
+ ///
+ public string? ChannelCode { get; set; }
+
+ ///
+ /// 服务渠道展示名。
+ ///
+ public string? ServiceChannelName { get; set; }
+
+ ///
+ /// 订单状态码。
+ ///
+ public string OrderStatus { get; set; } = string.Empty;
+
+ ///
+ /// 订单状态名称。
+ ///
+ public string? OrderStatusName { get; set; }
+}
diff --git a/src/Oms.Application/Services/Operations/OutboundRecordService.cs b/src/Oms.Application/Services/Operations/OutboundRecordService.cs
index 4393969..2e92f1d 100644
--- a/src/Oms.Application/Services/Operations/OutboundRecordService.cs
+++ b/src/Oms.Application/Services/Operations/OutboundRecordService.cs
@@ -2,8 +2,12 @@ using Fake.Application;
using Microsoft.AspNetCore.Mvc;
using Oms.Application.Dtos.Operations;
using Oms.Application.Dtos.Shared;
+using Oms.Application.Shared;
+using Oms.Domain.ChannelsAggregate.Repositories;
using Oms.Domain.OperationsAggregate.Entities;
using Oms.Domain.OperationsAggregate.Repositories;
+using Oms.Domain.OrdersAggregate.Consts;
+using Oms.Domain.OrdersAggregate.Repositories;
using Oms.Domain.SystemAggregate.Repositories;
namespace Oms.Application.Services.Operations;
@@ -15,7 +19,73 @@ namespace Oms.Application.Services.Operations;
public class OutboundRecordService : ApplicationService
{
private IOutboundRecordRepository OutboundRecordRepository => LazyServiceProvider.GetRequiredService();
+ private IOrderRepository OrderRepository => LazyServiceProvider.GetRequiredService();
+ private ISalesProductRepository SalesProductRepository => LazyServiceProvider.GetRequiredService();
+ private IServiceChannelRepository ServiceChannelRepository => LazyServiceProvider.GetRequiredService();
+ private IServiceChannelGroupRepository ServiceChannelGroupRepository => LazyServiceProvider.GetRequiredService();
private IWarehouseAddressRepository WarehouseAddressRepository => LazyServiceProvider.GetRequiredService();
+ private ICountryRepository CountryRepository => LazyServiceProvider.GetRequiredService();
+
+ ///
+ /// 按单号查询订单(出库扫描展示销售产品、目的国、服务渠道,只读)。
+ ///
+ [HttpGet("/operation/outbound/order-lookup")]
+ public async Task LookupOrderAsync([FromQuery] string waybillNo)
+ {
+ if (string.IsNullOrWhiteSpace(waybillNo))
+ {
+ throw new BusinessException("请扫描或输入单号");
+ }
+
+ var order = await OrderRepository.GetByWaybillNoAsync(waybillNo.Trim());
+ if (order == null)
+ {
+ throw new BusinessException("未找到对应订单,请检查单号");
+ }
+
+ EnsureOutboundAllowed(order.OrderStatus);
+
+ var output = new OutboundOrderLookupOutput
+ {
+ OrderId = order.Id,
+ ServiceNo = order.ServiceNo,
+ ClientNo = order.ClientNo,
+ TrackNo = order.TrackNo,
+ ProductCode = order.ProductCode,
+ DestinationCountry = order.ConsigneeCountry,
+ OrderStatus = order.OrderStatus,
+ OrderStatusName = OmsDocHelper.GetOrderStatusName(order.OrderStatus)
+ };
+
+ if (!string.IsNullOrWhiteSpace(order.ProductCode))
+ {
+ var product = await SalesProductRepository.GetByProductCodeAsync(order.ProductCode.Trim());
+ if (product != null)
+ {
+ output.SalesProductId = product.Id;
+ output.SalesProductName = product.ProductName;
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(order.ConsigneeCountry))
+ {
+ var country = await CountryRepository.FirstOrDefaultAsync(x => x.CountryCode == order.ConsigneeCountry);
+ output.DestinationCountryName = country?.ChineseName ?? country?.EnglishName ?? order.ConsigneeCountry;
+ }
+
+ if (!string.IsNullOrWhiteSpace(order.ChannelCode))
+ {
+ var channel = await ServiceChannelRepository.GetByChannelCodeAsync(order.ChannelCode.Trim());
+ if (channel != null)
+ {
+ output.ServiceChannelId = channel.Id;
+ output.ChannelCode = channel.ChannelCode;
+ output.ServiceChannelName = await BuildServiceChannelDisplayNameAsync(channel.GroupId, channel.ChannelName);
+ }
+ }
+
+ return output;
+ }
///
/// 新增出库记录。
@@ -31,12 +101,25 @@ public class OutboundRecordService : ApplicationService
[HttpPost("/operation/outbound/create")]
public async Task CreateAsync(CreateOutboundRecordInput input)
{
+ if (string.IsNullOrWhiteSpace(input.WaybillNo))
+ {
+ throw new BusinessException("运单号不能为空");
+ }
+
+ var order = await OrderRepository.GetByWaybillNoAsync(input.WaybillNo.Trim());
+ if (order == null)
+ {
+ throw new BusinessException("未找到对应订单,请检查单号");
+ }
+
+ EnsureOutboundAllowed(order.OrderStatus);
+
var now = DateTime.Now;
var operatorId = CurrentUser.GetId();
var entity = new OutboundRecord
{
- WaybillNo = input.WaybillNo,
- WarehouseId = input.WarehouseId,
+ WaybillNo = input.WaybillNo.Trim(),
+ WarehouseId = input.WarehouseId ?? order.WarehouseId,
OperateTime = now,
OperatorId = operatorId
};
@@ -86,4 +169,30 @@ public class OutboundRecordService : ApplicationService
return new OmsItemsResult { Items = items };
}
+
+ private static void EnsureOutboundAllowed(string orderStatus)
+ {
+ if (orderStatus is OrderStatusCodes.PendingPayment or OrderStatusCodes.Received)
+ {
+ throw new BusinessException("订单未付款,禁止出库");
+ }
+
+ if (orderStatus != OrderStatusCodes.PendingShip)
+ {
+ throw new BusinessException($"订单状态为「{OmsDocHelper.GetOrderStatusName(orderStatus)}」,不允许出库");
+ }
+ }
+
+ private async Task BuildServiceChannelDisplayNameAsync(int? groupId, string channelName)
+ {
+ string? groupName = null;
+ if (groupId.HasValue)
+ {
+ var group = await ServiceChannelGroupRepository.FirstOrDefaultAsync(x => x.Id == groupId.Value);
+ groupName = group?.Name;
+ }
+
+ var displayName = $"{groupName}{channelName}".Trim();
+ return string.IsNullOrWhiteSpace(displayName) ? channelName : displayName;
+ }
}
diff --git a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
index b52cdf3..c4b45a4 100644
--- a/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.ParcelBilling.cs
@@ -1142,7 +1142,7 @@ public partial class OrderService
OrderFeeChargingStageCodes.Inbound,
replaceExistingReceivable: true);
- order.OrderStatus = OrderStatusCodes.Received;
+ order.OrderStatus = OrderStatusCodes.PendingPayment;
order.ReceiveTime = DateTime.Now;
order.UpdateTime = DateTime.Now;
await OrderRepository.UpdateAsync(order);
diff --git a/src/Oms.Application/Services/Orders/OrderService.cs b/src/Oms.Application/Services/Orders/OrderService.cs
index 1b794bb..7f21b87 100644
--- a/src/Oms.Application/Services/Orders/OrderService.cs
+++ b/src/Oms.Application/Services/Orders/OrderService.cs
@@ -119,7 +119,7 @@ public partial class OrderService : ApplicationService
throw new BusinessException("订单已支付");
}
- if (order.OrderStatus != OrderStatusCodes.PendingPayment)
+ if (order.OrderStatus is not (OrderStatusCodes.PendingPayment or OrderStatusCodes.Received))
{
throw new BusinessException("当前订单状态不可支付");
}